@atproto/ozone 0.1.63 → 0.1.65
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.
- package/CHANGELOG.md +24 -0
- package/dist/api/moderation/queryEvents.d.ts.map +1 -1
- package/dist/api/moderation/queryEvents.js +2 -1
- package/dist/api/moderation/queryEvents.js.map +1 -1
- package/dist/daemon/blob-diverter.d.ts +26 -11
- package/dist/daemon/blob-diverter.d.ts.map +1 -1
- package/dist/daemon/blob-diverter.js +109 -52
- package/dist/daemon/blob-diverter.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +30 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +15 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts +2 -0
- package/dist/lexicon/types/tools/ozone/moderation/defs.d.ts.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/defs.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts +1 -0
- package/dist/lexicon/types/tools/ozone/moderation/queryEvents.d.ts.map +1 -1
- package/dist/mod-service/index.d.ts +1 -0
- package/dist/mod-service/index.d.ts.map +1 -1
- package/dist/mod-service/index.js +12 -1
- package/dist/mod-service/index.js.map +1 -1
- package/dist/mod-service/views.d.ts.map +1 -1
- package/dist/mod-service/views.js +8 -0
- package/dist/mod-service/views.js.map +1 -1
- package/dist/setting/constants.d.ts +1 -0
- package/dist/setting/constants.d.ts.map +1 -1
- package/dist/setting/constants.js +2 -1
- package/dist/setting/constants.js.map +1 -1
- package/dist/setting/validators.d.ts.map +1 -1
- package/dist/setting/validators.js +19 -0
- package/dist/setting/validators.js.map +1 -1
- package/dist/util.d.ts +2 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +9 -20
- package/dist/util.js.map +1 -1
- package/package.json +10 -11
- package/src/api/moderation/queryEvents.ts +2 -0
- package/src/daemon/blob-diverter.ts +137 -102
- package/src/lexicon/lexicons.ts +17 -0
- package/src/lexicon/types/tools/ozone/moderation/defs.ts +2 -0
- package/src/lexicon/types/tools/ozone/moderation/queryEvents.ts +1 -0
- package/src/mod-service/index.ts +14 -0
- package/src/mod-service/views.ts +11 -0
- package/src/setting/constants.ts +1 -0
- package/src/setting/validators.ts +28 -1
- package/src/util.ts +8 -20
- package/tests/_util.ts +46 -0
- package/tests/blob-divert.test.ts +30 -19
- package/tests/moderation.test.ts +3 -1
- package/tests/server.test.ts +23 -35
- package/tests/takedown.test.ts +64 -0
- package/tsconfig.tests.tsbuildinfo +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import assert from 'node:assert'
|
|
2
1
|
import {
|
|
3
2
|
ModeratorClient,
|
|
4
3
|
SeedClient,
|
|
5
4
|
TestNetwork,
|
|
6
5
|
basicSeed,
|
|
7
6
|
} from '@atproto/dev-env'
|
|
7
|
+
import { ResponseType, XRPCError } from '@atproto/xrpc'
|
|
8
|
+
import assert from 'node:assert'
|
|
8
9
|
import { forSnapshot } from './_util'
|
|
9
10
|
|
|
10
11
|
describe('blob divert', () => {
|
|
@@ -30,13 +31,16 @@ describe('blob divert', () => {
|
|
|
30
31
|
await network.close()
|
|
31
32
|
})
|
|
32
33
|
|
|
33
|
-
const mockReportServiceResponse = (
|
|
34
|
+
const mockReportServiceResponse = (succeeds: boolean) => {
|
|
34
35
|
const blobDiverter = network.ozone.ctx.blobDiverter
|
|
35
36
|
assert(blobDiverter)
|
|
36
37
|
return jest
|
|
37
|
-
.spyOn(blobDiverter, '
|
|
38
|
+
.spyOn(blobDiverter, 'uploadBlob')
|
|
38
39
|
.mockImplementation(async () => {
|
|
39
|
-
|
|
40
|
+
if (!succeeds) {
|
|
41
|
+
// Using an XRPCError to trigger retries
|
|
42
|
+
throw new XRPCError(ResponseType.Unknown, undefined)
|
|
43
|
+
}
|
|
40
44
|
})
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -46,6 +50,8 @@ describe('blob divert', () => {
|
|
|
46
50
|
cid: sc.posts[sc.dids.carol][0].ref.cidStr,
|
|
47
51
|
})
|
|
48
52
|
|
|
53
|
+
const getImages = () => sc.posts[sc.dids.carol][0].images
|
|
54
|
+
|
|
49
55
|
const emitDivertEvent = async () =>
|
|
50
56
|
modClient.emitEvent(
|
|
51
57
|
{
|
|
@@ -55,9 +61,7 @@ describe('blob divert', () => {
|
|
|
55
61
|
comment: 'Diverting for test',
|
|
56
62
|
},
|
|
57
63
|
createdBy: sc.dids.alice,
|
|
58
|
-
subjectBlobCids:
|
|
59
|
-
img.image.ref.toString(),
|
|
60
|
-
),
|
|
64
|
+
subjectBlobCids: getImages().map((img) => img.image.ref.toString()),
|
|
61
65
|
},
|
|
62
66
|
'moderator',
|
|
63
67
|
)
|
|
@@ -65,25 +69,32 @@ describe('blob divert', () => {
|
|
|
65
69
|
it('fails and keeps attempt count when report service fails to accept upload.', async () => {
|
|
66
70
|
// Simulate failure to fail upload
|
|
67
71
|
const reportServiceRequest = mockReportServiceResponse(false)
|
|
72
|
+
try {
|
|
73
|
+
await expect(emitDivertEvent()).rejects.toThrow('Failed to process blobs')
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
// 1 initial attempt + 3 retries
|
|
76
|
+
expect(reportServiceRequest).toHaveBeenCalledTimes(getImages().length * 4)
|
|
77
|
+
} finally {
|
|
78
|
+
reportServiceRequest.mockRestore()
|
|
79
|
+
}
|
|
72
80
|
})
|
|
73
81
|
|
|
74
82
|
it('sends blobs to configured divert service and marks divert date', async () => {
|
|
75
|
-
// Simulate
|
|
83
|
+
// Simulate success to accept upload
|
|
76
84
|
const reportServiceRequest = mockReportServiceResponse(true)
|
|
85
|
+
try {
|
|
86
|
+
const divertEvent = await emitDivertEvent()
|
|
77
87
|
|
|
78
|
-
|
|
88
|
+
expect(reportServiceRequest).toHaveBeenCalledTimes(getImages().length)
|
|
89
|
+
expect(forSnapshot(divertEvent)).toMatchSnapshot()
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const { subjectStatuses } = await modClient.queryStatuses({
|
|
84
|
-
subject: getSubject().uri,
|
|
85
|
-
})
|
|
91
|
+
const { subjectStatuses } = await modClient.queryStatuses({
|
|
92
|
+
subject: getSubject().uri,
|
|
93
|
+
})
|
|
86
94
|
|
|
87
|
-
|
|
95
|
+
expect(subjectStatuses[0].takendown).toBe(true)
|
|
96
|
+
} finally {
|
|
97
|
+
reportServiceRequest.mockRestore()
|
|
98
|
+
}
|
|
88
99
|
})
|
|
89
100
|
})
|
package/tests/moderation.test.ts
CHANGED
|
@@ -816,7 +816,9 @@ describe('moderation', () => {
|
|
|
816
816
|
it.skip('prevents image blob from being served, even when cached.', async () => {
|
|
817
817
|
const fetchImage = await fetch(imageUri)
|
|
818
818
|
expect(fetchImage.status).toEqual(404)
|
|
819
|
-
expect(await fetchImage.json()).
|
|
819
|
+
expect(await fetchImage.json()).toMatchObject({
|
|
820
|
+
message: 'Blob not found',
|
|
821
|
+
})
|
|
820
822
|
})
|
|
821
823
|
|
|
822
824
|
it('invalidates the image in the cdn', async () => {
|
package/tests/server.test.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TestNetwork, TestOzone } from '@atproto/dev-env'
|
|
2
2
|
import express from 'express'
|
|
3
|
-
import axios, { AxiosError } from 'axios'
|
|
4
|
-
import { TestOzone, TestNetwork } from '@atproto/dev-env'
|
|
5
3
|
import { handler as errorHandler } from '../src/error'
|
|
4
|
+
import { startServer } from './_util'
|
|
6
5
|
|
|
7
6
|
describe('server', () => {
|
|
8
7
|
let network: TestNetwork
|
|
@@ -20,56 +19,45 @@ describe('server', () => {
|
|
|
20
19
|
})
|
|
21
20
|
|
|
22
21
|
it('preserves 404s.', async () => {
|
|
23
|
-
const
|
|
24
|
-
|
|
22
|
+
const response = await fetch(`${ozone.url}/unknown`)
|
|
23
|
+
expect(response.status).toEqual(404)
|
|
25
24
|
})
|
|
26
25
|
|
|
27
26
|
it('error handler turns unknown errors into 500s.', async () => {
|
|
28
27
|
const app = express()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
const promise = axios.get(`http://localhost:${port}/oops`)
|
|
36
|
-
await expect(promise).rejects.toThrow('failed with status code 500')
|
|
37
|
-
srv.close()
|
|
28
|
+
.get('/oops', () => {
|
|
29
|
+
throw new Error('Oops!')
|
|
30
|
+
})
|
|
31
|
+
.use(errorHandler)
|
|
32
|
+
|
|
33
|
+
const { origin, stop } = await startServer(app)
|
|
38
34
|
try {
|
|
39
|
-
await
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
expect(axiosError.response?.status).toEqual(500)
|
|
43
|
-
expect(axiosError.response?.data).toEqual({
|
|
35
|
+
const response = await fetch(new URL(`/oops`, origin))
|
|
36
|
+
expect(response.status).toEqual(500)
|
|
37
|
+
await expect(response.json()).resolves.toEqual({
|
|
44
38
|
error: 'InternalServerError',
|
|
45
39
|
message: 'Internal Server Error',
|
|
46
40
|
})
|
|
41
|
+
} finally {
|
|
42
|
+
await stop()
|
|
47
43
|
}
|
|
48
44
|
})
|
|
49
45
|
|
|
50
46
|
it('healthcheck succeeds when database is available.', async () => {
|
|
51
|
-
const
|
|
52
|
-
expect(status).toEqual(200)
|
|
53
|
-
expect(
|
|
47
|
+
const response = await fetch(`${network.bsky.url}/xrpc/_health`)
|
|
48
|
+
expect(response.status).toEqual(200)
|
|
49
|
+
await expect(response.json()).resolves.toEqual({ version: 'unknown' })
|
|
54
50
|
})
|
|
55
51
|
|
|
56
52
|
it('healthcheck fails when database is unavailable.', async () => {
|
|
57
53
|
// destroy sequencer to release connection that would prevent the db from closing
|
|
58
54
|
await ozone.ctx.sequencer.destroy()
|
|
59
55
|
await ozone.ctx.db.close()
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (axios.isAxiosError(err)) {
|
|
66
|
-
error = err
|
|
67
|
-
} else {
|
|
68
|
-
throw err
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
expect(error.response?.status).toEqual(503)
|
|
72
|
-
expect(error.response?.data).toEqual({
|
|
56
|
+
|
|
57
|
+
const res = await fetch(`${ozone.url}/xrpc/_health`)
|
|
58
|
+
|
|
59
|
+
expect(res.status).toEqual(503)
|
|
60
|
+
await expect(res.json()).resolves.toEqual({
|
|
73
61
|
version: '0.0.0',
|
|
74
62
|
error: 'Service Unavailable',
|
|
75
63
|
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TestNetwork,
|
|
3
|
+
TestOzone,
|
|
4
|
+
SeedClient,
|
|
5
|
+
basicSeed,
|
|
6
|
+
ModeratorClient,
|
|
7
|
+
} from '@atproto/dev-env'
|
|
8
|
+
import { AtpAgent } from '@atproto/api'
|
|
9
|
+
|
|
10
|
+
describe('moderation', () => {
|
|
11
|
+
let network: TestNetwork
|
|
12
|
+
let ozone: TestOzone
|
|
13
|
+
let agent: AtpAgent
|
|
14
|
+
let bskyAgent: AtpAgent
|
|
15
|
+
let pdsAgent: AtpAgent
|
|
16
|
+
let sc: SeedClient
|
|
17
|
+
let modClient: ModeratorClient
|
|
18
|
+
|
|
19
|
+
const repoSubject = (did: string) => ({
|
|
20
|
+
$type: 'com.atproto.admin.defs#repoRef',
|
|
21
|
+
did,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
network = await TestNetwork.create({
|
|
26
|
+
dbPostgresSchema: 'ozone_takedown',
|
|
27
|
+
})
|
|
28
|
+
ozone = network.ozone
|
|
29
|
+
agent = network.ozone.getClient()
|
|
30
|
+
bskyAgent = network.bsky.getClient()
|
|
31
|
+
pdsAgent = network.pds.getClient()
|
|
32
|
+
sc = network.getSeedClient()
|
|
33
|
+
modClient = network.ozone.getModClient()
|
|
34
|
+
await basicSeed(sc)
|
|
35
|
+
await network.processAll()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
afterAll(async () => {
|
|
39
|
+
await network.close()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('allows specifying policy for takedown actions.', async () => {
|
|
43
|
+
await modClient.performTakedown({
|
|
44
|
+
subject: repoSubject(sc.dids.bob),
|
|
45
|
+
policies: ['trolling'],
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Verify that that the takedown even exposes the policy specified for it
|
|
49
|
+
const { events } = await modClient.queryEvents({
|
|
50
|
+
subject: sc.dids.bob,
|
|
51
|
+
types: ['tools.ozone.moderation.defs#modEventTakedown'],
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(events[0].event.policies?.[0]).toEqual('trolling')
|
|
55
|
+
|
|
56
|
+
// Verify that event stream can be filtered by policy
|
|
57
|
+
const { events: filteredEvents } = await modClient.queryEvents({
|
|
58
|
+
subject: sc.dids.bob,
|
|
59
|
+
policies: ['trolling'],
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
expect(filteredEvents[0].subject.did).toEqual(sc.dids.bob)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./tests/3p-labeler.test.ts","./tests/_util.ts","./tests/ack-all-subjects-of-account.test.ts","./tests/blob-divert.test.ts","./tests/communication-templates.test.ts","./tests/content-tagger.test.ts","./tests/db.test.ts","./tests/get-config.test.ts","./tests/get-lists.test.ts","./tests/get-profiles.test.ts","./tests/get-record.test.ts","./tests/get-records.test.ts","./tests/get-repo.test.ts","./tests/get-repos.test.ts","./tests/get-starter-pack.test.ts","./tests/moderation-appeals.test.ts","./tests/moderation-events.test.ts","./tests/moderation-status-tags.test.ts","./tests/moderation-statuses.test.ts","./tests/moderation.test.ts","./tests/protected-tags.test.ts","./tests/query-labels.test.ts","./tests/record-and-account-events.test.ts","./tests/repo-search.test.ts","./tests/report-muting.test.ts","./tests/sequencer.test.ts","./tests/server.test.ts","./tests/sets.test.ts","./tests/settings.test.ts","./tests/team.test.ts"],"version":"5.6.3"}
|
|
1
|
+
{"root":["./tests/3p-labeler.test.ts","./tests/_util.ts","./tests/ack-all-subjects-of-account.test.ts","./tests/blob-divert.test.ts","./tests/communication-templates.test.ts","./tests/content-tagger.test.ts","./tests/db.test.ts","./tests/get-config.test.ts","./tests/get-lists.test.ts","./tests/get-profiles.test.ts","./tests/get-record.test.ts","./tests/get-records.test.ts","./tests/get-repo.test.ts","./tests/get-repos.test.ts","./tests/get-starter-pack.test.ts","./tests/moderation-appeals.test.ts","./tests/moderation-events.test.ts","./tests/moderation-status-tags.test.ts","./tests/moderation-statuses.test.ts","./tests/moderation.test.ts","./tests/protected-tags.test.ts","./tests/query-labels.test.ts","./tests/record-and-account-events.test.ts","./tests/repo-search.test.ts","./tests/report-muting.test.ts","./tests/sequencer.test.ts","./tests/server.test.ts","./tests/sets.test.ts","./tests/settings.test.ts","./tests/takedown.test.ts","./tests/team.test.ts"],"version":"5.6.3"}
|