@atproto/ozone 0.2.5 → 0.2.6

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 (146) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +2 -0
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/report/queryActivities.d.ts +4 -0
  6. package/dist/api/report/queryActivities.d.ts.map +1 -0
  7. package/dist/api/report/queryActivities.js +36 -0
  8. package/dist/api/report/queryActivities.js.map +1 -0
  9. package/dist/background.d.ts +5 -3
  10. package/dist/background.d.ts.map +1 -1
  11. package/dist/background.js +13 -4
  12. package/dist/background.js.map +1 -1
  13. package/dist/context.js +1 -1
  14. package/dist/context.js.map +1 -1
  15. package/dist/daemon/context.js +1 -1
  16. package/dist/daemon/context.js.map +1 -1
  17. package/dist/daemon/verification-listener.d.ts +1 -1
  18. package/dist/daemon/verification-listener.d.ts.map +1 -1
  19. package/dist/daemon/verification-listener.js +10 -4
  20. package/dist/daemon/verification-listener.js.map +1 -1
  21. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.d.ts +4 -0
  22. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.d.ts.map +1 -0
  23. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.js +15 -0
  24. package/dist/db/migrations/20260602T120000000Z-add-report-activity-created-index.js.map +1 -0
  25. package/dist/db/migrations/index.d.ts +1 -0
  26. package/dist/db/migrations/index.d.ts.map +1 -1
  27. package/dist/db/migrations/index.js +1 -0
  28. package/dist/db/migrations/index.js.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +23 -13
  31. package/dist/index.js.map +1 -1
  32. package/dist/jetstream/service.d.ts +1 -1
  33. package/dist/jetstream/service.d.ts.map +1 -1
  34. package/dist/jetstream/service.js +3 -1
  35. package/dist/jetstream/service.js.map +1 -1
  36. package/dist/lexicon/index.d.ts +11 -0
  37. package/dist/lexicon/index.d.ts.map +1 -1
  38. package/dist/lexicon/index.js +18 -0
  39. package/dist/lexicon/index.js.map +1 -1
  40. package/dist/lexicon/lexicons.d.ts +338 -0
  41. package/dist/lexicon/lexicons.d.ts.map +1 -1
  42. package/dist/lexicon/lexicons.js +173 -0
  43. package/dist/lexicon/lexicons.js.map +1 -1
  44. package/dist/lexicon/types/app/bsky/notification/defs.d.ts +1 -0
  45. package/dist/lexicon/types/app/bsky/notification/defs.d.ts.map +1 -1
  46. package/dist/lexicon/types/app/bsky/notification/defs.js.map +1 -1
  47. package/dist/lexicon/types/chat/bsky/notification/defs.d.ts +19 -0
  48. package/dist/lexicon/types/chat/bsky/notification/defs.d.ts.map +1 -0
  49. package/dist/lexicon/types/chat/bsky/notification/defs.js +19 -0
  50. package/dist/lexicon/types/chat/bsky/notification/defs.js.map +1 -0
  51. package/dist/lexicon/types/chat/bsky/notification/getPreferences.d.ts +20 -0
  52. package/dist/lexicon/types/chat/bsky/notification/getPreferences.d.ts.map +1 -0
  53. package/dist/lexicon/types/chat/bsky/notification/getPreferences.js +5 -0
  54. package/dist/lexicon/types/chat/bsky/notification/getPreferences.js.map +1 -0
  55. package/dist/lexicon/types/chat/bsky/notification/putPreferences.d.ts +26 -0
  56. package/dist/lexicon/types/chat/bsky/notification/putPreferences.d.ts.map +1 -0
  57. package/dist/lexicon/types/chat/bsky/notification/putPreferences.js +5 -0
  58. package/dist/lexicon/types/chat/bsky/notification/putPreferences.js.map +1 -0
  59. package/dist/lexicon/types/tools/ozone/report/defs.d.ts +1 -0
  60. package/dist/lexicon/types/tools/ozone/report/defs.d.ts.map +1 -1
  61. package/dist/lexicon/types/tools/ozone/report/defs.js.map +1 -1
  62. package/dist/lexicon/types/tools/ozone/report/queryActivities.d.ts +32 -0
  63. package/dist/lexicon/types/tools/ozone/report/queryActivities.d.ts.map +1 -0
  64. package/dist/lexicon/types/tools/ozone/report/queryActivities.js +5 -0
  65. package/dist/lexicon/types/tools/ozone/report/queryActivities.js.map +1 -0
  66. package/dist/mod-service/report.d.ts +1 -0
  67. package/dist/mod-service/report.d.ts.map +1 -1
  68. package/dist/mod-service/report.js +16 -0
  69. package/dist/mod-service/report.js.map +1 -1
  70. package/dist/report/activity.d.ts +19 -1
  71. package/dist/report/activity.d.ts.map +1 -1
  72. package/dist/report/activity.js +27 -1
  73. package/dist/report/activity.js.map +1 -1
  74. package/package.json +9 -8
  75. package/src/api/index.ts +2 -0
  76. package/src/api/report/queryActivities.ts +64 -0
  77. package/src/background.ts +19 -4
  78. package/src/context.ts +1 -1
  79. package/src/daemon/context.ts +1 -1
  80. package/src/daemon/verification-listener.ts +9 -4
  81. package/src/db/migrations/20260602T120000000Z-add-report-activity-created-index.ts +17 -0
  82. package/src/db/migrations/index.ts +1 -0
  83. package/src/index.ts +25 -15
  84. package/src/jetstream/service.ts +3 -1
  85. package/src/mod-service/report.ts +19 -0
  86. package/src/report/activity.ts +47 -0
  87. package/tests/3p-labeler.test.ts +2 -2
  88. package/tests/_util.ts +8 -25
  89. package/tests/account-strikes.test.ts +1 -1
  90. package/tests/ack-all-subjects-of-account.test.ts +1 -1
  91. package/tests/age-assurance.test.ts +1 -1
  92. package/tests/blob-divert.test.ts +1 -1
  93. package/tests/communication-templates.test.ts +1 -1
  94. package/tests/content-tagger.test.ts +1 -1
  95. package/tests/db.test.ts +1 -1
  96. package/tests/expiring-label.test.ts +1 -1
  97. package/tests/expiring-tags.test.ts +1 -1
  98. package/tests/get-account-timeline.test.ts +1 -1
  99. package/tests/get-config.test.ts +1 -1
  100. package/tests/get-lists.test.ts +2 -1
  101. package/tests/get-profiles.test.ts +1 -1
  102. package/tests/get-record.test.ts +1 -1
  103. package/tests/get-records.test.ts +1 -1
  104. package/tests/get-repo.test.ts +1 -1
  105. package/tests/get-report.test.ts +1 -1
  106. package/tests/get-reporter-stats.test.ts +1 -1
  107. package/tests/get-repos.test.ts +1 -1
  108. package/tests/get-starter-pack.test.ts +1 -1
  109. package/tests/get-subjects.test.ts +1 -1
  110. package/tests/mod-tool.test.ts +1 -1
  111. package/tests/moderation-appeals.test.ts +1 -1
  112. package/tests/moderation-events.test.ts +1 -1
  113. package/tests/moderation-status-tags.test.ts +1 -1
  114. package/tests/moderation-statuses.test.ts +1 -1
  115. package/tests/moderation.test.ts +1 -1
  116. package/tests/protected-tags.test.ts +1 -1
  117. package/tests/query-labels.test.ts +1 -1
  118. package/tests/query-reports.test.ts +1 -1
  119. package/tests/queue-assignment.test.ts +1 -1
  120. package/tests/queue-router.test.ts +1 -1
  121. package/tests/queues.test.ts +1 -1
  122. package/tests/record-and-account-events.test.ts +1 -1
  123. package/tests/repo-search.test.ts +2 -2
  124. package/tests/report-action.test.ts +1 -1
  125. package/tests/report-activity.test.ts +145 -1
  126. package/tests/report-assignment.test.ts +1 -1
  127. package/tests/report-muting.test.ts +1 -1
  128. package/tests/report-reason.test.ts +1 -1
  129. package/tests/report-reassign-queue.test.ts +1 -1
  130. package/tests/report-routing.test.ts +1 -1
  131. package/tests/report-stats.test.ts +1 -1
  132. package/tests/revoke-account-credentials.test.ts +1 -1
  133. package/tests/safelink.test.ts +1 -1
  134. package/tests/scheduled-action-processor.test.ts +1 -1
  135. package/tests/scheduled-action.test.ts +1 -1
  136. package/tests/sequencer.test.ts +1 -1
  137. package/tests/server.test.ts +9 -12
  138. package/tests/sets.test.ts +1 -1
  139. package/tests/settings.test.ts +1 -1
  140. package/tests/strike-expiry-processor.test.ts +1 -1
  141. package/tests/subject-priority-score.test.ts +1 -1
  142. package/tests/takedown.test.ts +1 -1
  143. package/tests/team.test.ts +1 -1
  144. package/tests/verification-listener.test.ts +40 -13
  145. package/tests/verification.test.ts +1 -1
  146. package/tsconfig.build.tsbuildinfo +1 -1
package/tests/_util.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { Server } from 'node:http'
1
+ import { RequestListener, createServer } from 'node:http'
2
2
  import { AddressInfo } from 'node:net'
3
- import { type Express } from 'express'
3
+ // eslint-disable-next-line import/default
4
+ import httpTerminator from 'http-terminator'
4
5
  import { CID } from 'multiformats/cid'
5
6
  import { lexToJson } from '@atproto/lexicon'
6
7
  import { AtUri } from '@atproto/syntax'
@@ -215,19 +216,11 @@ export const stripViewerFromThread = <T extends ThreadViewPost>(
215
216
  return thread
216
217
  }
217
218
 
218
- export async function startServer(app: Express) {
219
- return new Promise<{
220
- origin: string
221
- server: Server
222
- stop: () => Promise<void>
223
- }>((resolve, reject) => {
219
+ export async function startServer(listener: RequestListener) {
220
+ return new Promise<AsyncDisposable & { port: number }>((resolve, reject) => {
224
221
  const onListen = () => {
225
222
  const port = (server.address() as AddressInfo).port
226
- resolve({
227
- server,
228
- origin: `http://localhost:${port}`,
229
- stop: () => stopServer(server),
230
- })
223
+ resolve({ port, [Symbol.asyncDispose]: () => terminator.terminate() })
231
224
  cleanup()
232
225
  }
233
226
  const onError = (err: Error) => {
@@ -239,21 +232,11 @@ export async function startServer(app: Express) {
239
232
  server.removeListener('error', onError)
240
233
  }
241
234
 
242
- const server = app
235
+ const server = createServer(listener)
243
236
  .listen(0)
244
237
  .once('listening', onListen)
245
238
  .once('error', onError)
246
- })
247
- }
248
239
 
249
- export async function stopServer(server: Server) {
250
- return new Promise<void>((resolve, reject) => {
251
- server.close((err) => {
252
- if (err) {
253
- reject(err)
254
- } else {
255
- resolve()
256
- }
257
- })
240
+ const terminator = httpTerminator.createHttpTerminator({ server })
258
241
  })
259
242
  }
@@ -65,7 +65,7 @@ describe('account-strikes', () => {
65
65
  })
66
66
 
67
67
  afterAll(async () => {
68
- await network.close()
68
+ await network?.close()
69
69
  })
70
70
 
71
71
  it('tracks strikes and exposes them through queryStatuses and queryEvents', async () => {
@@ -93,7 +93,7 @@ describe('acknowledge all subjects of account', () => {
93
93
  })
94
94
 
95
95
  afterAll(async () => {
96
- await network.close()
96
+ await network?.close()
97
97
  })
98
98
 
99
99
  it('acknowledges all open/escalated review subjects with takedown.', async () => {
@@ -22,7 +22,7 @@ describe('age assurance events', () => {
22
22
  })
23
23
 
24
24
  afterAll(async () => {
25
- await network.close()
25
+ await network?.close()
26
26
  })
27
27
 
28
28
  it('handles age assurance events from user', async () => {
@@ -30,7 +30,7 @@ describe('blob divert', () => {
30
30
  })
31
31
 
32
32
  afterAll(async () => {
33
- await network.close()
33
+ await network?.close()
34
34
  })
35
35
 
36
36
  const mockReportServiceResponse = (succeeds: boolean) => {
@@ -18,7 +18,7 @@ describe('communication-templates', () => {
18
18
  })
19
19
 
20
20
  afterAll(async () => {
21
- await network.close()
21
+ await network?.close()
22
22
  })
23
23
 
24
24
  const templateOne = {
@@ -23,7 +23,7 @@ describe('moderation subject content tagging', () => {
23
23
  })
24
24
 
25
25
  afterAll(async () => {
26
- await network.close()
26
+ await network?.close()
27
27
  })
28
28
 
29
29
  const getStatus = async (subject: string) => {
package/tests/db.test.ts CHANGED
@@ -15,7 +15,7 @@ describe('db', () => {
15
15
  })
16
16
 
17
17
  afterAll(async () => {
18
- await network.close()
18
+ await network?.close()
19
19
  })
20
20
 
21
21
  it('handles client errors without crashing.', async () => {
@@ -25,7 +25,7 @@ describe('expiring label', () => {
25
25
  })
26
26
 
27
27
  afterAll(async () => {
28
- await network.close()
28
+ await network?.close()
29
29
  })
30
30
 
31
31
  const emitExpiringLabel = async (did: string) =>
@@ -23,7 +23,7 @@ describe('expiring tags', () => {
23
23
  })
24
24
 
25
25
  afterAll(async () => {
26
- await network.close()
26
+ await network?.close()
27
27
  })
28
28
 
29
29
  const emitTagEvent = (
@@ -62,7 +62,7 @@ describe('account timeline', () => {
62
62
  })
63
63
 
64
64
  afterAll(async () => {
65
- await network.close()
65
+ await network?.close()
66
66
  })
67
67
 
68
68
  it('Returns entire timeline of events for a given account', async () => {
@@ -19,7 +19,7 @@ describe('get-config', () => {
19
19
  })
20
20
 
21
21
  afterAll(async () => {
22
- await network.close()
22
+ await network?.close()
23
23
  })
24
24
 
25
25
  const getConfig = async (role: 'moderator' | 'admin' | 'triage') => {
@@ -35,8 +35,9 @@ describe('admin get lists', () => {
35
35
  })
36
36
 
37
37
  afterAll(async () => {
38
+ // @TODO figure out why we even need this in afterAll ?
38
39
  AtpAgent.configure({ appLabelers: [BSKY_LABELER_DID] })
39
- await network.close()
40
+ await network?.close()
40
41
  })
41
42
 
42
43
  const getAlicesList = async () => {
@@ -26,7 +26,7 @@ describe('get profiles through ozone', () => {
26
26
  })
27
27
 
28
28
  afterAll(async () => {
29
- await network.close()
29
+ await network?.close()
30
30
  })
31
31
 
32
32
  it('allows getting profiles by dids for takendown accounts.', async () => {
@@ -38,7 +38,7 @@ describe('admin get record view', () => {
38
38
  })
39
39
 
40
40
  afterAll(async () => {
41
- await network.close()
41
+ await network?.close()
42
42
  })
43
43
 
44
44
  beforeAll(async () => {
@@ -37,7 +37,7 @@ describe('admin get records view', () => {
37
37
  })
38
38
 
39
39
  afterAll(async () => {
40
- await network.close()
40
+ await network?.close()
41
41
  })
42
42
 
43
43
  beforeAll(async () => {
@@ -43,7 +43,7 @@ describe('admin get repo view', () => {
43
43
  })
44
44
 
45
45
  afterAll(async () => {
46
- await network.close()
46
+ await network?.close()
47
47
  })
48
48
 
49
49
  beforeAll(async () => {
@@ -29,7 +29,7 @@ describe('ozone-get-report', () => {
29
29
  })
30
30
 
31
31
  afterAll(async () => {
32
- await network.close()
32
+ await network?.close()
33
33
  })
34
34
 
35
35
  it('returns a single report by id', async () => {
@@ -28,7 +28,7 @@ describe('reporter-stats', () => {
28
28
  })
29
29
 
30
30
  afterAll(async () => {
31
- await network.close()
31
+ await network?.close()
32
32
  })
33
33
 
34
34
  const getReporterStats = async (
@@ -43,7 +43,7 @@ describe('admin get multiple repos', () => {
43
43
  })
44
44
 
45
45
  afterAll(async () => {
46
- await network.close()
46
+ await network?.close()
47
47
  })
48
48
 
49
49
  beforeAll(async () => {
@@ -30,7 +30,7 @@ describe('admin get starter pack view', () => {
30
30
  })
31
31
 
32
32
  afterAll(async () => {
33
- await network.close()
33
+ await network?.close()
34
34
  })
35
35
 
36
36
  beforeAll(async () => {
@@ -37,7 +37,7 @@ describe('admin get multiple subjects with all relevant details', () => {
37
37
  })
38
38
 
39
39
  afterAll(async () => {
40
- await network.close()
40
+ await network?.close()
41
41
  })
42
42
 
43
43
  beforeAll(async () => {
@@ -21,7 +21,7 @@ describe('mod-tool tracking', () => {
21
21
  })
22
22
 
23
23
  afterAll(async () => {
24
- await network.close()
24
+ await network?.close()
25
25
  })
26
26
 
27
27
  it('stores and returns modTool with name and meta metadata', async () => {
@@ -30,7 +30,7 @@ describe('moderation-appeals', () => {
30
30
  })
31
31
 
32
32
  afterAll(async () => {
33
- await network.close()
33
+ await network?.close()
34
34
  })
35
35
 
36
36
  const assertSubjectStatus = async (
@@ -75,7 +75,7 @@ describe('moderation-events', () => {
75
75
  })
76
76
 
77
77
  afterAll(async () => {
78
- await network.close()
78
+ await network?.close()
79
79
  })
80
80
 
81
81
  describe('query events', () => {
@@ -24,7 +24,7 @@ describe('moderation-status-tags', () => {
24
24
  })
25
25
 
26
26
  afterAll(async () => {
27
- await network.close()
27
+ await network?.close()
28
28
  })
29
29
 
30
30
  describe('manage tags on subject status', () => {
@@ -80,7 +80,7 @@ describe('moderation-statuses', () => {
80
80
  })
81
81
 
82
82
  afterAll(async () => {
83
- await network.close()
83
+ await network?.close()
84
84
  })
85
85
 
86
86
  describe('query statuses', () => {
@@ -81,7 +81,7 @@ describe('moderation', () => {
81
81
  })
82
82
 
83
83
  afterAll(async () => {
84
- await network.close()
84
+ await network?.close()
85
85
  })
86
86
 
87
87
  describe('reporting', () => {
@@ -30,7 +30,7 @@ describe('protected-tags', () => {
30
30
  })
31
31
 
32
32
  afterAll(async () => {
33
- await network.close()
33
+ await network?.close()
34
34
  })
35
35
 
36
36
  describe('Settings management', () => {
@@ -70,7 +70,7 @@ describe('ozone query labels', () => {
70
70
  })
71
71
 
72
72
  afterAll(async () => {
73
- await network.close()
73
+ await network?.close()
74
74
  })
75
75
 
76
76
  it('returns all labels', async () => {
@@ -91,7 +91,7 @@ describe('query-reports', () => {
91
91
  })
92
92
 
93
93
  afterAll(async () => {
94
- await network.close()
94
+ await network?.close()
95
95
  })
96
96
 
97
97
  describe('queryReports', () => {
@@ -106,7 +106,7 @@ describe('queue', () => {
106
106
  })
107
107
 
108
108
  afterAll(async () => {
109
- await network.close()
109
+ await network?.close()
110
110
  })
111
111
 
112
112
  it('get active assignments', async () => {
@@ -91,7 +91,7 @@ describe('queue-router', () => {
91
91
  })
92
92
 
93
93
  afterAll(async () => {
94
- await network.close()
94
+ await network?.close()
95
95
  })
96
96
 
97
97
  it('inserts report rows with no queue assignment when no queues are configured', async () => {
@@ -109,7 +109,7 @@ describe('ozone-queues', () => {
109
109
  })
110
110
 
111
111
  afterAll(async () => {
112
- await network.close()
112
+ await network?.close()
113
113
  })
114
114
 
115
115
  describe('createQueue', () => {
@@ -32,7 +32,7 @@ describe('record and account events on moderation subjects', () => {
32
32
  })
33
33
 
34
34
  afterAll(async () => {
35
- await network.close()
35
+ await network?.close()
36
36
  })
37
37
 
38
38
  const getSubjectStatus = async (
@@ -28,10 +28,10 @@ describe('admin repo search view', () => {
28
28
  ids.ToolsOzoneModerationSearchRepos,
29
29
  )
30
30
  await network.processAll()
31
- }, 20_000) // @NOTE seeding can take a while
31
+ }, 40_000) // @NOTE seeding can take a while
32
32
 
33
33
  afterAll(async () => {
34
- await network.close()
34
+ await network?.close()
35
35
  })
36
36
 
37
37
  beforeAll(async () => {
@@ -29,7 +29,7 @@ describe('report-action', () => {
29
29
  })
30
30
 
31
31
  afterAll(async () => {
32
- await network.close()
32
+ await network?.close()
33
33
  })
34
34
 
35
35
  describe('emitEvent with reportAction', () => {
@@ -75,7 +75,7 @@ describe('report-activity', () => {
75
75
  })
76
76
 
77
77
  afterAll(async () => {
78
- await network.close()
78
+ await network?.close()
79
79
  })
80
80
 
81
81
  describe('createActivity — noteActivity', () => {
@@ -564,4 +564,148 @@ describe('report-activity', () => {
564
564
  expect(data.activities.some((a) => a.reportId === reportB.id)).toBe(false)
565
565
  })
566
566
  })
567
+
568
+ describe('queryActivities', () => {
569
+ const queryActivities = async (
570
+ params: {
571
+ activityTypes?: string[]
572
+ createdAfter?: string
573
+ createdBefore?: string
574
+ sortDirection?: 'asc' | 'desc'
575
+ limit?: number
576
+ cursor?: string
577
+ },
578
+ role: 'admin' | 'triage' = 'admin',
579
+ ) => {
580
+ return agent.tools.ozone.report.queryActivities(params, {
581
+ headers: await network.ozone.modHeaders(
582
+ ids.ToolsOzoneReportQueryActivities,
583
+ role,
584
+ ),
585
+ })
586
+ }
587
+
588
+ it('hydrates the report on each activity', async () => {
589
+ const report = await createReport(sc.dids.alice)
590
+ await createActivity({
591
+ reportId: report.id,
592
+ activity: { $type: `${DEFS}#closeActivity` },
593
+ })
594
+
595
+ const { data } = await queryActivities({
596
+ activityTypes: ['closeActivity'],
597
+ sortDirection: 'desc',
598
+ limit: 100,
599
+ })
600
+ const hit = data.activities.find((a) => a.reportId === report.id)
601
+ expect(hit).toBeDefined()
602
+ expect(hit!.report).toBeDefined()
603
+ expect(hit!.report!.id).toBe(report.id)
604
+ expect(hit!.report!.subject).toBeDefined()
605
+ })
606
+
607
+ it('filters by activity types across reports', async () => {
608
+ const reportA = await createReport(sc.dids.alice)
609
+ const reportB = await createReport(sc.dids.bob)
610
+ await createActivity({
611
+ reportId: reportA.id,
612
+ activity: { $type: `${DEFS}#noteActivity` },
613
+ internalNote: 'note',
614
+ })
615
+ await createActivity({
616
+ reportId: reportA.id,
617
+ activity: { $type: `${DEFS}#closeActivity` },
618
+ })
619
+ await createActivity({
620
+ reportId: reportB.id,
621
+ activity: { $type: `${DEFS}#escalationActivity` },
622
+ })
623
+
624
+ const { data } = await queryActivities({
625
+ activityTypes: ['closeActivity', 'escalationActivity'],
626
+ sortDirection: 'asc',
627
+ })
628
+
629
+ const types = new Set(data.activities.map((a) => a.activity.$type))
630
+ expect(types.has(`${DEFS}#noteActivity`)).toBe(false)
631
+ expect(types.has(`${DEFS}#closeActivity`)).toBe(true)
632
+ expect(types.has(`${DEFS}#escalationActivity`)).toBe(true)
633
+ const reportIds = new Set(data.activities.map((a) => a.reportId))
634
+ expect(reportIds.has(reportA.id)).toBe(true)
635
+ expect(reportIds.has(reportB.id)).toBe(true)
636
+ })
637
+
638
+ it('paginates ascending across multiple reports with stable cursor', async () => {
639
+ const reportA = await createReport(sc.dids.alice)
640
+ const reportB = await createReport(sc.dids.bob)
641
+ const created: number[] = []
642
+ for (let i = 0; i < 3; i++) {
643
+ const r = await createActivity({
644
+ reportId: i % 2 === 0 ? reportA.id : reportB.id,
645
+ activity: { $type: `${DEFS}#noteActivity` },
646
+ internalNote: `n-${i}`,
647
+ })
648
+ created.push(r.data.activity.id)
649
+ }
650
+ const minId = Math.min(...created)
651
+
652
+ const seen: number[] = []
653
+ let cursor: string | undefined
654
+ let pages = 0
655
+ do {
656
+ const { data } = await queryActivities({
657
+ sortDirection: 'asc',
658
+ limit: 2,
659
+ cursor,
660
+ })
661
+ for (const a of data.activities) {
662
+ if (a.id >= minId) seen.push(a.id)
663
+ }
664
+ cursor = data.cursor
665
+ pages++
666
+ if (pages > 50) break // safety net for runaway loops
667
+ } while (cursor)
668
+
669
+ // The created activities should appear in ascending ID order, no dupes.
670
+ const sortedCreated = [...created].sort((a, b) => a - b)
671
+ const seenForCreated = seen.filter((id) => created.includes(id))
672
+ expect(seenForCreated).toEqual(sortedCreated)
673
+ })
674
+
675
+ it('respects createdBefore and createdAfter bounds', async () => {
676
+ const report = await createReport(sc.dids.alice)
677
+ const before = await createActivity({
678
+ reportId: report.id,
679
+ activity: { $type: `${DEFS}#noteActivity` },
680
+ internalNote: 'before',
681
+ })
682
+ // Small wait so the next activity's createdAt is strictly later.
683
+ await new Promise((r) => setTimeout(r, 50))
684
+ const cutoff = new Date().toISOString()
685
+ await new Promise((r) => setTimeout(r, 50))
686
+ const after = await createActivity({
687
+ reportId: report.id,
688
+ activity: { $type: `${DEFS}#noteActivity` },
689
+ internalNote: 'after',
690
+ })
691
+
692
+ const beforeRes = await queryActivities({
693
+ createdBefore: cutoff,
694
+ sortDirection: 'asc',
695
+ limit: 100,
696
+ })
697
+ const beforeIds = new Set(beforeRes.data.activities.map((a) => a.id))
698
+ expect(beforeIds.has(before.data.activity.id)).toBe(true)
699
+ expect(beforeIds.has(after.data.activity.id)).toBe(false)
700
+
701
+ const afterRes = await queryActivities({
702
+ createdAfter: cutoff,
703
+ sortDirection: 'asc',
704
+ limit: 100,
705
+ })
706
+ const afterIds = new Set(afterRes.data.activities.map((a) => a.id))
707
+ expect(afterIds.has(before.data.activity.id)).toBe(false)
708
+ expect(afterIds.has(after.data.activity.id)).toBe(true)
709
+ })
710
+ })
567
711
  })
@@ -132,7 +132,7 @@ describe('report-assignment', () => {
132
132
  })
133
133
 
134
134
  afterAll(async () => {
135
- await network.close()
135
+ await network?.close()
136
136
  })
137
137
 
138
138
  it('can get assignment history', async () => {
@@ -29,7 +29,7 @@ describe('report-muting', () => {
29
29
  })
30
30
 
31
31
  afterAll(async () => {
32
- await network.close()
32
+ await network?.close()
33
33
  })
34
34
 
35
35
  const assertSubjectStatus = async (
@@ -45,7 +45,7 @@ describe('report reason', () => {
45
45
  })
46
46
 
47
47
  afterAll(async () => {
48
- await network.close()
48
+ await network?.close()
49
49
  })
50
50
 
51
51
  describe('createReport', () => {
@@ -143,7 +143,7 @@ describe('report-reassign-queue', () => {
143
143
  })
144
144
 
145
145
  afterAll(async () => {
146
- await network.close()
146
+ await network?.close()
147
147
  })
148
148
 
149
149
  describe('happy path: assigning to a real queue', () => {
@@ -122,7 +122,7 @@ describe('queue-router', () => {
122
122
  })
123
123
 
124
124
  afterAll(async () => {
125
- await network.close()
125
+ await network?.close()
126
126
  })
127
127
 
128
128
  it('routes unassigned AND unmatched reports to a newly created queue', async () => {
@@ -119,7 +119,7 @@ describe('report-stats', () => {
119
119
  })
120
120
 
121
121
  afterAll(async () => {
122
- await network.close()
122
+ await network?.close()
123
123
  })
124
124
 
125
125
  describe('aggregate', () => {
@@ -21,7 +21,7 @@ describe('revoke account credentials event', () => {
21
21
  })
22
22
 
23
23
  afterAll(async () => {
24
- await network.close()
24
+ await network?.close()
25
25
  })
26
26
 
27
27
  it('fails on non account subjects and for non admins', async () => {