@atproto/ozone 0.2.9 → 0.2.11
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 +26 -0
- package/package.json +25 -21
- package/bin/migration-create.ts +0 -38
- package/jest.config.cjs +0 -22
- package/src/api/chat/getActorMetadata.ts +0 -23
- package/src/api/chat/getConvo.ts +0 -23
- package/src/api/chat/getConvoMembers.ts +0 -23
- package/src/api/chat/getConvos.ts +0 -23
- package/src/api/chat/getMessageContext.ts +0 -42
- package/src/api/chat/index.ts +0 -16
- package/src/api/communication/createTemplate.ts +0 -51
- package/src/api/communication/deleteTemplate.ts +0 -23
- package/src/api/communication/listTemplates.ts +0 -31
- package/src/api/communication/updateTemplate.ts +0 -51
- package/src/api/health.ts +0 -27
- package/src/api/index.ts +0 -146
- package/src/api/label/fetchLabels.ts +0 -32
- package/src/api/label/queryLabels.ts +0 -57
- package/src/api/label/subscribeLabels.ts +0 -25
- package/src/api/moderation/cancelScheduledActions.ts +0 -72
- package/src/api/moderation/emitEvent.ts +0 -475
- package/src/api/moderation/getAccountTimeline.ts +0 -160
- package/src/api/moderation/getEvent.ts +0 -19
- package/src/api/moderation/getRecord.ts +0 -40
- package/src/api/moderation/getRecords.ts +0 -50
- package/src/api/moderation/getRepo.ts +0 -34
- package/src/api/moderation/getReporterStats.ts +0 -18
- package/src/api/moderation/getRepos.ts +0 -41
- package/src/api/moderation/getSubjects.ts +0 -101
- package/src/api/moderation/listScheduledActions.ts +0 -45
- package/src/api/moderation/queryEvents.ts +0 -72
- package/src/api/moderation/queryStatuses.ts +0 -23
- package/src/api/moderation/scheduleAction.ts +0 -129
- package/src/api/moderation/searchRepos.ts +0 -46
- package/src/api/moderation/util.ts +0 -96
- package/src/api/proxied.ts +0 -327
- package/src/api/queue/assignModerator.ts +0 -31
- package/src/api/queue/createQueue.ts +0 -62
- package/src/api/queue/deleteQueue.ts +0 -56
- package/src/api/queue/getAssignments.ts +0 -19
- package/src/api/queue/listQueues.ts +0 -39
- package/src/api/queue/routeReports.ts +0 -44
- package/src/api/queue/unassignModerator.ts +0 -26
- package/src/api/queue/updateQueue.ts +0 -54
- package/src/api/report/assignModerator.ts +0 -36
- package/src/api/report/createActivity.ts +0 -57
- package/src/api/report/createReport.ts +0 -93
- package/src/api/report/getAssignments.ts +0 -20
- package/src/api/report/getHistoricalStats.ts +0 -41
- package/src/api/report/getLatestReport.ts +0 -44
- package/src/api/report/getLiveStats.ts +0 -26
- package/src/api/report/getReport.ts +0 -55
- package/src/api/report/listActivities.ts +0 -37
- package/src/api/report/queryActivities.ts +0 -64
- package/src/api/report/queryReports.ts +0 -44
- package/src/api/report/reassignQueue.ts +0 -68
- package/src/api/report/refreshStats.ts +0 -27
- package/src/api/report/unassignModerator.ts +0 -21
- package/src/api/safelink/addRule.ts +0 -48
- package/src/api/safelink/queryEvents.ts +0 -32
- package/src/api/safelink/queryRules.ts +0 -58
- package/src/api/safelink/removeRule.ts +0 -42
- package/src/api/safelink/updateRule.ts +0 -48
- package/src/api/server/getConfig.ts +0 -35
- package/src/api/set/addValues.ts +0 -28
- package/src/api/set/deleteSet.ts +0 -34
- package/src/api/set/deleteValues.ts +0 -31
- package/src/api/set/getValues.ts +0 -42
- package/src/api/set/querySets.ts +0 -36
- package/src/api/set/upsertSet.ts +0 -38
- package/src/api/setting/listOptions.ts +0 -44
- package/src/api/setting/removeOptions.ts +0 -64
- package/src/api/setting/upsertOption.ts +0 -156
- package/src/api/team/addMember.ts +0 -51
- package/src/api/team/deleteMember.ts +0 -29
- package/src/api/team/listMembers.ts +0 -20
- package/src/api/team/updateMember.ts +0 -47
- package/src/api/util.ts +0 -265
- package/src/api/verification/grantVerifications.ts +0 -90
- package/src/api/verification/listVerifications.ts +0 -44
- package/src/api/verification/revokeVerifications.ts +0 -43
- package/src/api/well-known.ts +0 -46
- package/src/assignment/index.ts +0 -728
- package/src/auth-verifier.ts +0 -227
- package/src/background.ts +0 -183
- package/src/communication-service/template.ts +0 -110
- package/src/communication-service/util.ts +0 -8
- package/src/config/config.ts +0 -211
- package/src/config/env.ts +0 -95
- package/src/config/index.ts +0 -3
- package/src/config/secrets.ts +0 -17
- package/src/context.ts +0 -399
- package/src/daemon/blob-diverter.ts +0 -186
- package/src/daemon/context.ts +0 -247
- package/src/daemon/event-pusher.ts +0 -363
- package/src/daemon/event-reverser.ts +0 -128
- package/src/daemon/index.ts +0 -33
- package/src/daemon/job-cursor.ts +0 -33
- package/src/daemon/materialized-view-refresher.ts +0 -33
- package/src/daemon/queue-router.ts +0 -101
- package/src/daemon/scheduled-action-processor.ts +0 -304
- package/src/daemon/stats-computer.ts +0 -101
- package/src/daemon/strike-expiry-processor.ts +0 -95
- package/src/daemon/team-profile-synchronizer.ts +0 -15
- package/src/daemon/verification-listener.ts +0 -169
- package/src/db/index.ts +0 -203
- package/src/db/migrations/20231219T205730722Z-init.ts +0 -170
- package/src/db/migrations/20240116T085607200Z-communication-template.ts +0 -23
- package/src/db/migrations/20240201T051104136Z-mod-event-blobs.ts +0 -15
- package/src/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.ts +0 -31
- package/src/db/migrations/20240228T003647759Z-add-label-sigs.ts +0 -25
- package/src/db/migrations/20240408T192432676Z-mute-reporting.ts +0 -15
- package/src/db/migrations/20240506T225055595Z-message-subject.ts +0 -21
- package/src/db/migrations/20240521T211332580Z-member.ts +0 -17
- package/src/db/migrations/20240814T003647759Z-event-created-at-index.ts +0 -13
- package/src/db/migrations/20240903T205730722Z-add-template-lang.ts +0 -12
- package/src/db/migrations/20240904T205730722Z-add-subject-did-index.ts +0 -13
- package/src/db/migrations/20241001T205730722Z-subject-status-review-state-index.ts +0 -15
- package/src/db/migrations/20241008T205730722Z-sets.ts +0 -53
- package/src/db/migrations/20241018T205730722Z-setting.ts +0 -27
- package/src/db/migrations/20241026T205730722Z-add-hosting-status-to-subject-status.ts +0 -57
- package/src/db/migrations/20241220T144630860Z-stats-materialized-views.ts +0 -215
- package/src/db/migrations/20250204T003647759Z-add-subject-priority-score.ts +0 -22
- package/src/db/migrations/20250211T003647759Z-add-reporter-stats-index.ts +0 -38
- package/src/db/migrations/20250211T132135150Z-moderation-event-message-partial-idx.ts +0 -26
- package/src/db/migrations/20250221T132135150Z-member-details.ts +0 -14
- package/src/db/migrations/20250404T201720309Z-subject-status-sort-idxs.ts +0 -18
- package/src/db/migrations/20250415T201720309Z-verification.ts +0 -34
- package/src/db/migrations/20250417T201720309Z-firehose-cursor.ts +0 -16
- package/src/db/migrations/20250609T110704000Z-safelink.ts +0 -53
- package/src/db/migrations/20250618T180246000Z-add-mod-tool-to-moderation-event.ts +0 -18
- package/src/db/migrations/20250701T000000000Z-add-age-assurance-state.ts +0 -25
- package/src/db/migrations/20250715T000000000Z-add-mod-event-external-id.ts +0 -15
- package/src/db/migrations/20250718T150931000Z-update-appeal-reason-stats.ts +0 -310
- package/src/db/migrations/20250813T000000000Z-mod-tool-batch-id-index.ts +0 -14
- package/src/db/migrations/20250923T000000000Z-scheduled-actions.ts +0 -56
- package/src/db/migrations/20251008T120000000Z-add-strike-system.ts +0 -87
- package/src/db/migrations/20260210T154806448Z-mod-event-created-by-indexes.ts +0 -22
- package/src/db/migrations/20260219T164523000Z-create-report-table.ts +0 -155
- package/src/db/migrations/20260219T165302248Z-moderator-assignment.ts +0 -42
- package/src/db/migrations/20260225T000000000Z-add-report-queue-table.ts +0 -41
- package/src/db/migrations/20260313T000000000Z-add-report-activity-table.ts +0 -48
- package/src/db/migrations/20260318T152058935Z-add-report-stat.ts +0 -35
- package/src/db/migrations/20260428T000000000Z-add-expiring-tag-table.ts +0 -32
- package/src/db/migrations/20260513T202941104Z-add-subject-convo-id.ts +0 -114
- package/src/db/migrations/20260602T120000000Z-add-report-activity-created-index.ts +0 -17
- package/src/db/migrations/index.ts +0 -44
- package/src/db/migrations/provider.ts +0 -26
- package/src/db/pagination.ts +0 -335
- package/src/db/schema/account_events_stats.ts +0 -16
- package/src/db/schema/account_record_events_stats.ts +0 -15
- package/src/db/schema/account_record_status_stats.ts +0 -15
- package/src/db/schema/account_strike.ts +0 -13
- package/src/db/schema/blob_push_event.ts +0 -21
- package/src/db/schema/communication_template.ts +0 -19
- package/src/db/schema/expiring_tag.ts +0 -18
- package/src/db/schema/firehose_cursor.ts +0 -13
- package/src/db/schema/index.ts +0 -60
- package/src/db/schema/job_cursor.ts +0 -13
- package/src/db/schema/label.ts +0 -22
- package/src/db/schema/member.ts +0 -22
- package/src/db/schema/moderation_event.ts +0 -61
- package/src/db/schema/moderation_subject_status.ts +0 -52
- package/src/db/schema/moderator_assignment.ts +0 -16
- package/src/db/schema/ozone_set.ts +0 -24
- package/src/db/schema/record_events_stats.ts +0 -15
- package/src/db/schema/record_push_event.ts +0 -21
- package/src/db/schema/repo_push_event.ts +0 -19
- package/src/db/schema/report.ts +0 -28
- package/src/db/schema/report_activity.ts +0 -22
- package/src/db/schema/report_queue.ts +0 -21
- package/src/db/schema/report_stat.ts +0 -27
- package/src/db/schema/safelink.ts +0 -39
- package/src/db/schema/scheduled-action.ts +0 -25
- package/src/db/schema/setting.ts +0 -24
- package/src/db/schema/signing_key.ts +0 -10
- package/src/db/schema/verification.ts +0 -21
- package/src/db/types.ts +0 -24
- package/src/error.ts +0 -12
- package/src/image-invalidator.ts +0 -7
- package/src/index.ts +0 -154
- package/src/jetstream/service.ts +0 -107
- package/src/logger.ts +0 -29
- package/src/mod-service/expiring-tags.ts +0 -104
- package/src/mod-service/index.ts +0 -1842
- package/src/mod-service/profile.ts +0 -139
- package/src/mod-service/report.ts +0 -429
- package/src/mod-service/status.ts +0 -549
- package/src/mod-service/strike.ts +0 -96
- package/src/mod-service/subject.ts +0 -311
- package/src/mod-service/types.ts +0 -96
- package/src/mod-service/util.ts +0 -99
- package/src/mod-service/views.ts +0 -912
- package/src/queue/service.ts +0 -603
- package/src/report/activity.ts +0 -281
- package/src/report/handle-report-update.ts +0 -209
- package/src/report/reassign.ts +0 -109
- package/src/report/stats.ts +0 -852
- package/src/report/views.ts +0 -239
- package/src/safelink/service.ts +0 -304
- package/src/scheduled-action/service.ts +0 -281
- package/src/scheduled-action/types.ts +0 -17
- package/src/sequencer/index.ts +0 -2
- package/src/sequencer/outbox.ts +0 -123
- package/src/sequencer/sequencer.ts +0 -147
- package/src/set/service.ts +0 -230
- package/src/setting/constants.ts +0 -3
- package/src/setting/service.ts +0 -148
- package/src/setting/types.ts +0 -3
- package/src/setting/validators.ts +0 -333
- package/src/tag-service/content-tagger.ts +0 -30
- package/src/tag-service/embed-tagger.ts +0 -70
- package/src/tag-service/index.ts +0 -70
- package/src/tag-service/language-data.ts +0 -561
- package/src/tag-service/language-tagger.ts +0 -101
- package/src/tag-service/util.ts +0 -13
- package/src/team/index.ts +0 -296
- package/src/util.ts +0 -230
- package/src/verification/issuer.ts +0 -146
- package/src/verification/service.ts +0 -208
- package/src/verification/util.ts +0 -53
- package/test.env +0 -2
- package/tests/3p-labeler.test.ts +0 -288
- package/tests/__snapshots__/account-strikes.test.ts.snap +0 -159
- package/tests/__snapshots__/age-assurance.test.ts.snap +0 -66
- package/tests/__snapshots__/blob-divert.test.ts.snap +0 -219
- package/tests/__snapshots__/get-account-timeline.test.ts.snap +0 -36
- package/tests/__snapshots__/get-record.test.ts.snap +0 -271
- package/tests/__snapshots__/get-records.test.ts.snap +0 -175
- package/tests/__snapshots__/get-repo.test.ts.snap +0 -91
- package/tests/__snapshots__/get-repos.test.ts.snap +0 -127
- package/tests/__snapshots__/get-starter-pack.test.ts.snap +0 -535
- package/tests/__snapshots__/get-subjects.test.ts.snap +0 -529
- package/tests/__snapshots__/moderation-events.test.ts.snap +0 -347
- package/tests/__snapshots__/moderation-statuses.test.ts.snap +0 -276
- package/tests/__snapshots__/moderation.test.ts.snap +0 -85
- package/tests/__snapshots__/report-reason.test.ts.snap +0 -14
- package/tests/__snapshots__/safelink.test.ts.snap +0 -179
- package/tests/__snapshots__/scheduled-action.test.ts.snap +0 -61
- package/tests/__snapshots__/sets.test.ts.snap +0 -46
- package/tests/__snapshots__/settings.test.ts.snap +0 -52
- package/tests/__snapshots__/team.test.ts.snap +0 -374
- package/tests/__snapshots__/verification-listener.test.ts.snap +0 -152
- package/tests/__snapshots__/verification.test.ts.snap +0 -302
- package/tests/_util.ts +0 -242
- package/tests/account-strikes.test.ts +0 -184
- package/tests/ack-all-subjects-of-account.test.ts +0 -177
- package/tests/age-assurance.test.ts +0 -372
- package/tests/blob-divert.test.ts +0 -106
- package/tests/communication-templates.test.ts +0 -149
- package/tests/content-tagger.test.ts +0 -170
- package/tests/db.test.ts +0 -184
- package/tests/expiring-label.test.ts +0 -72
- package/tests/expiring-tags.test.ts +0 -232
- package/tests/get-account-timeline.test.ts +0 -85
- package/tests/get-config.test.ts +0 -55
- package/tests/get-lists.test.ts +0 -111
- package/tests/get-profiles.test.ts +0 -70
- package/tests/get-record.test.ts +0 -130
- package/tests/get-records.test.ts +0 -91
- package/tests/get-repo.test.ts +0 -171
- package/tests/get-report.test.ts +0 -136
- package/tests/get-reporter-stats.test.ts +0 -132
- package/tests/get-repos.test.ts +0 -91
- package/tests/get-starter-pack.test.ts +0 -115
- package/tests/get-subjects.test.ts +0 -81
- package/tests/mod-tool.test.ts +0 -268
- package/tests/moderation-appeals.test.ts +0 -260
- package/tests/moderation-events.test.ts +0 -756
- package/tests/moderation-status-tags.test.ts +0 -140
- package/tests/moderation-statuses.test.ts +0 -495
- package/tests/moderation.test.ts +0 -992
- package/tests/protected-tags.test.ts +0 -218
- package/tests/query-labels.test.ts +0 -238
- package/tests/query-reports.test.ts +0 -608
- package/tests/queue-assignment.test.ts +0 -428
- package/tests/queue-router.test.ts +0 -306
- package/tests/queues.test.ts +0 -690
- package/tests/record-and-account-events.test.ts +0 -197
- package/tests/repo-search.test.ts +0 -136
- package/tests/report-action.test.ts +0 -308
- package/tests/report-activity.test.ts +0 -711
- package/tests/report-assignment.test.ts +0 -517
- package/tests/report-muting.test.ts +0 -100
- package/tests/report-reason.test.ts +0 -154
- package/tests/report-reassign-queue.test.ts +0 -340
- package/tests/report-routing.test.ts +0 -245
- package/tests/report-stats.test.ts +0 -545
- package/tests/revoke-account-credentials.test.ts +0 -54
- package/tests/safelink.test.ts +0 -534
- package/tests/scheduled-action-processor.test.ts +0 -488
- package/tests/scheduled-action.test.ts +0 -334
- package/tests/sequencer.test.ts +0 -227
- package/tests/server.test.ts +0 -62
- package/tests/sets.test.ts +0 -246
- package/tests/settings.test.ts +0 -308
- package/tests/strike-expiry-processor.test.ts +0 -299
- package/tests/subject-priority-score.test.ts +0 -96
- package/tests/takedown.test.ts +0 -105
- package/tests/team.test.ts +0 -216
- package/tests/verification-listener.test.ts +0 -129
- package/tests/verification.test.ts +0 -186
- package/tsconfig.build.json +0 -9
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -8
package/tests/moderation.test.ts
DELETED
|
@@ -1,992 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AtpAgent,
|
|
3
|
-
ChatBskyConvoDefs,
|
|
4
|
-
ToolsOzoneModerationEmitEvent,
|
|
5
|
-
} from '@atproto/api'
|
|
6
|
-
import { HOUR } from '@atproto/common'
|
|
7
|
-
import {
|
|
8
|
-
ImageRef,
|
|
9
|
-
ModeratorClient,
|
|
10
|
-
RecordRef,
|
|
11
|
-
SeedClient,
|
|
12
|
-
TestNetwork,
|
|
13
|
-
TestOzone,
|
|
14
|
-
basicSeed,
|
|
15
|
-
} from '@atproto/dev-env'
|
|
16
|
-
import { AtUri } from '@atproto/syntax'
|
|
17
|
-
import { ImageInvalidator } from '../src/image-invalidator.js'
|
|
18
|
-
import { EventReverser } from '../src/index.js'
|
|
19
|
-
import { ids } from '../src/lexicon/lexicons.js'
|
|
20
|
-
import {
|
|
21
|
-
REASONMISLEADING,
|
|
22
|
-
REASONOTHER,
|
|
23
|
-
REASONSPAM,
|
|
24
|
-
} from '../src/lexicon/types/com/atproto/moderation/defs.js'
|
|
25
|
-
import {
|
|
26
|
-
ModEventLabel,
|
|
27
|
-
REVIEWCLOSED,
|
|
28
|
-
REVIEWESCALATED,
|
|
29
|
-
} from '../src/lexicon/types/tools/ozone/moderation/defs.js'
|
|
30
|
-
import { TAKEDOWN_LABEL } from '../src/mod-service/index.js'
|
|
31
|
-
import { forSnapshot, identity } from './_util.js'
|
|
32
|
-
|
|
33
|
-
describe('moderation', () => {
|
|
34
|
-
let network: TestNetwork
|
|
35
|
-
let ozone: TestOzone
|
|
36
|
-
let mockInvalidator: MockInvalidator
|
|
37
|
-
let agent: AtpAgent
|
|
38
|
-
let bskyAgent: AtpAgent
|
|
39
|
-
let pdsAgent: AtpAgent
|
|
40
|
-
let sc: SeedClient
|
|
41
|
-
let modClient: ModeratorClient
|
|
42
|
-
|
|
43
|
-
const repoSubject = (did: string) => ({
|
|
44
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
45
|
-
did,
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
const recordSubject = (ref: RecordRef) => ({
|
|
49
|
-
$type: 'com.atproto.repo.strongRef',
|
|
50
|
-
uri: ref.uriStr,
|
|
51
|
-
cid: ref.cidStr,
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const getLabel = async (uri: string, val: string, neg = false) => {
|
|
55
|
-
return ozone.ctx.db.db
|
|
56
|
-
.selectFrom('label')
|
|
57
|
-
.selectAll()
|
|
58
|
-
.where('uri', '=', uri)
|
|
59
|
-
.where('val', '=', val)
|
|
60
|
-
.where('neg', '=', neg)
|
|
61
|
-
.executeTakeFirst()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
beforeAll(async () => {
|
|
65
|
-
mockInvalidator = new MockInvalidator()
|
|
66
|
-
network = await TestNetwork.create({
|
|
67
|
-
dbPostgresSchema: 'ozone_moderation',
|
|
68
|
-
ozone: {
|
|
69
|
-
imgInvalidator: mockInvalidator,
|
|
70
|
-
cdnPaths: ['/path1/%s/%s', '/path2/%s/%s'],
|
|
71
|
-
},
|
|
72
|
-
})
|
|
73
|
-
ozone = network.ozone
|
|
74
|
-
agent = network.ozone.getAgent()
|
|
75
|
-
bskyAgent = network.bsky.getAgent()
|
|
76
|
-
pdsAgent = network.pds.getAgent()
|
|
77
|
-
sc = network.getSeedClient()
|
|
78
|
-
modClient = network.ozone.getModClient()
|
|
79
|
-
await basicSeed(sc)
|
|
80
|
-
await network.processAll()
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
afterAll(async () => {
|
|
84
|
-
await network?.close()
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
describe('reporting', () => {
|
|
88
|
-
it('creates reports of a repo.', async () => {
|
|
89
|
-
const reportA = await sc.createReport({
|
|
90
|
-
reasonType: REASONSPAM,
|
|
91
|
-
subject: repoSubject(sc.dids.bob),
|
|
92
|
-
reportedBy: sc.dids.alice,
|
|
93
|
-
})
|
|
94
|
-
const reportB = await sc.createReport({
|
|
95
|
-
reasonType: REASONOTHER,
|
|
96
|
-
reason: 'impersonation',
|
|
97
|
-
subject: repoSubject(sc.dids.bob),
|
|
98
|
-
reportedBy: sc.dids.carol,
|
|
99
|
-
})
|
|
100
|
-
expect(forSnapshot([reportA, reportB])).toMatchSnapshot()
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it("allows reporting a repo that doesn't exist.", async () => {
|
|
104
|
-
const promise = sc.createReport({
|
|
105
|
-
reasonType: REASONSPAM,
|
|
106
|
-
subject: repoSubject('did:plc:unknown'),
|
|
107
|
-
reportedBy: sc.dids.alice,
|
|
108
|
-
})
|
|
109
|
-
await expect(promise).resolves.toBeDefined()
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('creates reports of a record.', async () => {
|
|
113
|
-
const postA = sc.posts[sc.dids.bob][0].ref
|
|
114
|
-
const postB = sc.posts[sc.dids.bob][1].ref
|
|
115
|
-
const reportA = await sc.createReport({
|
|
116
|
-
reportedBy: sc.dids.alice,
|
|
117
|
-
reasonType: REASONSPAM,
|
|
118
|
-
subject: recordSubject(postA),
|
|
119
|
-
})
|
|
120
|
-
const reportB = await sc.createReport({
|
|
121
|
-
reasonType: REASONOTHER,
|
|
122
|
-
reason: 'defamation',
|
|
123
|
-
subject: recordSubject(postB),
|
|
124
|
-
reportedBy: sc.dids.carol,
|
|
125
|
-
})
|
|
126
|
-
expect(forSnapshot([reportA, reportB])).toMatchSnapshot()
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it("allows reporting a record that doesn't exist.", async () => {
|
|
130
|
-
const postA = sc.posts[sc.dids.bob][0].ref
|
|
131
|
-
const postB = sc.posts[sc.dids.bob][1].ref
|
|
132
|
-
const postUriBad = new AtUri(postA.uriStr)
|
|
133
|
-
postUriBad.rkey = 'badrkey'
|
|
134
|
-
|
|
135
|
-
const promiseA = sc.createReport({
|
|
136
|
-
reasonType: REASONSPAM,
|
|
137
|
-
subject: {
|
|
138
|
-
$type: 'com.atproto.repo.strongRef',
|
|
139
|
-
uri: postUriBad.toString(),
|
|
140
|
-
cid: postA.cidStr,
|
|
141
|
-
},
|
|
142
|
-
reportedBy: sc.dids.alice,
|
|
143
|
-
})
|
|
144
|
-
await expect(promiseA).resolves.toBeDefined()
|
|
145
|
-
|
|
146
|
-
const promiseB = sc.createReport({
|
|
147
|
-
reasonType: REASONOTHER,
|
|
148
|
-
reason: 'defamation',
|
|
149
|
-
subject: {
|
|
150
|
-
$type: 'com.atproto.repo.strongRef',
|
|
151
|
-
uri: postB.uri.toString(),
|
|
152
|
-
cid: postA.cidStr, // bad cid
|
|
153
|
-
},
|
|
154
|
-
reportedBy: sc.dids.carol,
|
|
155
|
-
})
|
|
156
|
-
await expect(promiseB).resolves.toBeDefined()
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('creates reports of a DM chat.', async () => {
|
|
160
|
-
const messageId1 = 'testmessageid1'
|
|
161
|
-
const messageId2 = 'testmessageid2'
|
|
162
|
-
const reportA = await sc.createReport({
|
|
163
|
-
reportedBy: sc.dids.alice,
|
|
164
|
-
reasonType: REASONSPAM,
|
|
165
|
-
// @ts-expect-error "chat.bsky.convo.defs#messageRef" is not spec'd as subject
|
|
166
|
-
subject: identity<ChatBskyConvoDefs.MessageRef>({
|
|
167
|
-
$type: 'chat.bsky.convo.defs#messageRef',
|
|
168
|
-
did: sc.dids.carol,
|
|
169
|
-
messageId: messageId1,
|
|
170
|
-
convoId: 'testconvoid1',
|
|
171
|
-
}),
|
|
172
|
-
})
|
|
173
|
-
const reportB = await sc.createReport({
|
|
174
|
-
reportedBy: sc.dids.carol,
|
|
175
|
-
reasonType: REASONOTHER,
|
|
176
|
-
reason: 'defamation',
|
|
177
|
-
// @ts-expect-error "chat.bsky.convo.defs#messageRef" is not spec'd as subject
|
|
178
|
-
subject: identity<ChatBskyConvoDefs.MessageRef>({
|
|
179
|
-
$type: 'chat.bsky.convo.defs#messageRef',
|
|
180
|
-
did: sc.dids.carol,
|
|
181
|
-
messageId: messageId2,
|
|
182
|
-
// @ts-expect-error convoId intentionally missing, restore once this behavior is deprecated
|
|
183
|
-
convoId: undefined,
|
|
184
|
-
}),
|
|
185
|
-
})
|
|
186
|
-
expect(forSnapshot([reportA, reportB])).toMatchSnapshot()
|
|
187
|
-
const events = await ozone.ctx.db.db
|
|
188
|
-
.selectFrom('moderation_event')
|
|
189
|
-
.selectAll()
|
|
190
|
-
.where('subjectMessageId', 'in', [messageId1, messageId2])
|
|
191
|
-
.where('action', '=', 'tools.ozone.moderation.defs#modEventReport')
|
|
192
|
-
.execute()
|
|
193
|
-
expect(events.length).toBe(2)
|
|
194
|
-
expect(
|
|
195
|
-
events.every(
|
|
196
|
-
(row) => row.subjectType === 'chat.bsky.convo.defs#messageRef',
|
|
197
|
-
),
|
|
198
|
-
).toBe(true)
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('creates reports of convo', async () => {
|
|
202
|
-
const convoId1 = 'convoId1'
|
|
203
|
-
const convoId2 = 'convoId2'
|
|
204
|
-
const reportA = await sc.createReport({
|
|
205
|
-
reportedBy: sc.dids.alice,
|
|
206
|
-
reasonType: REASONSPAM,
|
|
207
|
-
subject: {
|
|
208
|
-
$type: 'chat.bsky.convo.defs#convoRef',
|
|
209
|
-
did: sc.dids.carol,
|
|
210
|
-
convoId: convoId1,
|
|
211
|
-
},
|
|
212
|
-
})
|
|
213
|
-
const reportB = await sc.createReport({
|
|
214
|
-
reportedBy: sc.dids.carol,
|
|
215
|
-
reasonType: REASONOTHER,
|
|
216
|
-
reason: 'defamation',
|
|
217
|
-
subject: {
|
|
218
|
-
$type: 'chat.bsky.convo.defs#convoRef',
|
|
219
|
-
did: sc.dids.carol,
|
|
220
|
-
convoId: convoId2,
|
|
221
|
-
},
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
// Verify reportA
|
|
225
|
-
expect(reportA.subject.$type).toBe('chat.bsky.convo.defs#convoRef')
|
|
226
|
-
expect(ChatBskyConvoDefs.isConvoRef(reportA.subject)).toBe(true)
|
|
227
|
-
if (ChatBskyConvoDefs.isConvoRef(reportA.subject)) {
|
|
228
|
-
expect(reportA.subject.convoId).toBe(convoId1)
|
|
229
|
-
expect(reportA.subject.did).toBe(sc.dids.carol)
|
|
230
|
-
}
|
|
231
|
-
expect(reportA.reasonType).toBe(REASONSPAM)
|
|
232
|
-
expect(reportA.reportedBy).toBe(sc.dids.alice)
|
|
233
|
-
expect(reportA.id).toBeGreaterThan(0)
|
|
234
|
-
|
|
235
|
-
// Verify reportB
|
|
236
|
-
expect(reportB.subject.$type).toBe('chat.bsky.convo.defs#convoRef')
|
|
237
|
-
expect(ChatBskyConvoDefs.isConvoRef(reportB.subject)).toBe(true)
|
|
238
|
-
if (ChatBskyConvoDefs.isConvoRef(reportB.subject)) {
|
|
239
|
-
expect(reportB.subject.convoId).toBe(convoId2)
|
|
240
|
-
expect(reportB.subject.did).toBe(sc.dids.carol)
|
|
241
|
-
}
|
|
242
|
-
expect(reportB.reasonType).toBe(REASONOTHER)
|
|
243
|
-
expect(reportB.reason).toBe('defamation')
|
|
244
|
-
expect(reportB.reportedBy).toBe(sc.dids.carol)
|
|
245
|
-
expect(reportB.id).toBeGreaterThan(reportA.id)
|
|
246
|
-
})
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
describe('actioning', () => {
|
|
250
|
-
it('resolves reports on repos and records.', async () => {
|
|
251
|
-
const post = sc.posts[sc.dids.bob][1].ref
|
|
252
|
-
|
|
253
|
-
await Promise.all([
|
|
254
|
-
sc.createReport({
|
|
255
|
-
reasonType: REASONSPAM,
|
|
256
|
-
subject: repoSubject(sc.dids.bob),
|
|
257
|
-
reportedBy: sc.dids.alice,
|
|
258
|
-
}),
|
|
259
|
-
sc.createReport({
|
|
260
|
-
reasonType: REASONOTHER,
|
|
261
|
-
reason: 'defamation',
|
|
262
|
-
subject: recordSubject(post),
|
|
263
|
-
reportedBy: sc.dids.carol,
|
|
264
|
-
}),
|
|
265
|
-
])
|
|
266
|
-
|
|
267
|
-
await modClient.performTakedown({
|
|
268
|
-
subject: repoSubject(sc.dids.bob),
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
const moderationStatusOnBobsAccount = await modClient.queryStatuses({
|
|
272
|
-
subject: sc.dids.bob,
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
// Validate that subject status is set to review closed and takendown flag is on
|
|
276
|
-
expect(moderationStatusOnBobsAccount.subjectStatuses[0]).toMatchObject({
|
|
277
|
-
reviewState: REVIEWCLOSED,
|
|
278
|
-
takendown: true,
|
|
279
|
-
subject: {
|
|
280
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
281
|
-
did: sc.dids.bob,
|
|
282
|
-
},
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
// Cleanup
|
|
286
|
-
await modClient.performReverseTakedown({
|
|
287
|
-
subject: repoSubject(sc.dids.bob),
|
|
288
|
-
})
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
it('supports escalating a subject', async () => {
|
|
292
|
-
const alicesPostRef = sc.posts[sc.dids.alice][0].ref
|
|
293
|
-
const alicesPostSubject = {
|
|
294
|
-
$type: 'com.atproto.repo.strongRef',
|
|
295
|
-
uri: alicesPostRef.uri.toString(),
|
|
296
|
-
cid: alicesPostRef.cid.toString(),
|
|
297
|
-
}
|
|
298
|
-
await modClient.emitEvent(
|
|
299
|
-
{
|
|
300
|
-
event: {
|
|
301
|
-
$type: 'tools.ozone.moderation.defs#modEventEscalate',
|
|
302
|
-
comment: 'Y',
|
|
303
|
-
},
|
|
304
|
-
subject: alicesPostSubject,
|
|
305
|
-
createdBy: 'did:example:admin',
|
|
306
|
-
},
|
|
307
|
-
'triage',
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
const alicesPostStatus = await modClient.queryStatuses({
|
|
311
|
-
subject: alicesPostRef.uri.toString(),
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
expect(alicesPostStatus.subjectStatuses[0]).toMatchObject({
|
|
315
|
-
reviewState: REVIEWESCALATED,
|
|
316
|
-
takendown: false,
|
|
317
|
-
subject: alicesPostSubject,
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('adds persistent comment on subject through comment event', async () => {
|
|
322
|
-
const alicesPostRef = sc.posts[sc.dids.alice][0].ref
|
|
323
|
-
const alicesPostSubject = {
|
|
324
|
-
$type: 'com.atproto.repo.strongRef',
|
|
325
|
-
uri: alicesPostRef.uri.toString(),
|
|
326
|
-
cid: alicesPostRef.cid.toString(),
|
|
327
|
-
}
|
|
328
|
-
await modClient.emitEvent(
|
|
329
|
-
{
|
|
330
|
-
event: {
|
|
331
|
-
$type: 'tools.ozone.moderation.defs#modEventComment',
|
|
332
|
-
sticky: true,
|
|
333
|
-
comment: 'This is a persistent note',
|
|
334
|
-
},
|
|
335
|
-
subject: alicesPostSubject,
|
|
336
|
-
createdBy: 'did:example:admin',
|
|
337
|
-
},
|
|
338
|
-
'triage',
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
const alicesPostStatus = await modClient.queryStatuses({
|
|
342
|
-
subject: alicesPostRef.uri.toString(),
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
expect(alicesPostStatus.subjectStatuses[0].comment).toEqual(
|
|
346
|
-
'This is a persistent note',
|
|
347
|
-
)
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
it('reverses status when revert event is triggered.', async () => {
|
|
351
|
-
const alicesPostRef = sc.posts[sc.dids.alice][0].ref
|
|
352
|
-
const emitModEvent = async (
|
|
353
|
-
event: ToolsOzoneModerationEmitEvent.InputSchema['event'],
|
|
354
|
-
overwrites: Partial<ToolsOzoneModerationEmitEvent.InputSchema> = {},
|
|
355
|
-
) => {
|
|
356
|
-
const baseAction = {
|
|
357
|
-
subject: {
|
|
358
|
-
$type: 'com.atproto.repo.strongRef',
|
|
359
|
-
uri: alicesPostRef.uriStr,
|
|
360
|
-
cid: alicesPostRef.cidStr,
|
|
361
|
-
},
|
|
362
|
-
createdBy: 'did:example:admin',
|
|
363
|
-
}
|
|
364
|
-
return modClient.emitEvent({
|
|
365
|
-
event,
|
|
366
|
-
...baseAction,
|
|
367
|
-
...overwrites,
|
|
368
|
-
})
|
|
369
|
-
}
|
|
370
|
-
// Validate that subject status is marked as escalated
|
|
371
|
-
await emitModEvent({
|
|
372
|
-
$type: 'tools.ozone.moderation.defs#modEventReport',
|
|
373
|
-
reportType: REASONSPAM,
|
|
374
|
-
})
|
|
375
|
-
await emitModEvent({
|
|
376
|
-
$type: 'tools.ozone.moderation.defs#modEventReport',
|
|
377
|
-
reportType: REASONMISLEADING,
|
|
378
|
-
})
|
|
379
|
-
await emitModEvent({
|
|
380
|
-
$type: 'tools.ozone.moderation.defs#modEventEscalate',
|
|
381
|
-
})
|
|
382
|
-
const alicesPostStatusAfterEscalation = await modClient.queryStatuses({
|
|
383
|
-
subject: alicesPostRef.uriStr,
|
|
384
|
-
})
|
|
385
|
-
expect(
|
|
386
|
-
alicesPostStatusAfterEscalation.subjectStatuses[0].reviewState,
|
|
387
|
-
).toEqual(REVIEWESCALATED)
|
|
388
|
-
|
|
389
|
-
// Validate that subject status is marked as takendown
|
|
390
|
-
|
|
391
|
-
await emitModEvent({
|
|
392
|
-
$type: 'tools.ozone.moderation.defs#modEventLabel',
|
|
393
|
-
createLabelVals: ['nsfw'],
|
|
394
|
-
negateLabelVals: [],
|
|
395
|
-
})
|
|
396
|
-
await emitModEvent({
|
|
397
|
-
$type: 'tools.ozone.moderation.defs#modEventTakedown',
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
const alicesPostStatusAfterTakedown = await modClient.queryStatuses({
|
|
401
|
-
subject: alicesPostRef.uriStr,
|
|
402
|
-
})
|
|
403
|
-
expect(alicesPostStatusAfterTakedown.subjectStatuses[0]).toMatchObject({
|
|
404
|
-
reviewState: REVIEWCLOSED,
|
|
405
|
-
takendown: true,
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
await emitModEvent({
|
|
409
|
-
$type: 'tools.ozone.moderation.defs#modEventReverseTakedown',
|
|
410
|
-
})
|
|
411
|
-
const alicesPostStatusAfterRevert = await modClient.queryStatuses({
|
|
412
|
-
subject: alicesPostRef.uriStr,
|
|
413
|
-
})
|
|
414
|
-
// Validate that after reverting, the status of the subject is reverted to the last status changing event
|
|
415
|
-
expect(alicesPostStatusAfterRevert.subjectStatuses[0]).toMatchObject({
|
|
416
|
-
reviewState: REVIEWCLOSED,
|
|
417
|
-
takendown: false,
|
|
418
|
-
})
|
|
419
|
-
// Validate that after reverting, the last review date of the subject
|
|
420
|
-
// DOES NOT update to the the last status changing event
|
|
421
|
-
expect(
|
|
422
|
-
new Date(
|
|
423
|
-
alicesPostStatusAfterEscalation.subjectStatuses[0]
|
|
424
|
-
.lastReviewedAt as string,
|
|
425
|
-
) <
|
|
426
|
-
new Date(
|
|
427
|
-
alicesPostStatusAfterRevert.subjectStatuses[0]
|
|
428
|
-
.lastReviewedAt as string,
|
|
429
|
-
),
|
|
430
|
-
).toBeTruthy()
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
it('negates an existing label.', async () => {
|
|
434
|
-
const { ctx } = ozone
|
|
435
|
-
const post = sc.posts[sc.dids.bob][0].ref
|
|
436
|
-
const bobsPostSubject = {
|
|
437
|
-
$type: 'com.atproto.repo.strongRef',
|
|
438
|
-
uri: post.uriStr,
|
|
439
|
-
cid: post.cidStr,
|
|
440
|
-
}
|
|
441
|
-
const modService = ctx.modService(ctx.db)
|
|
442
|
-
await modService.formatAndCreateLabels(post.uriStr, post.cidStr, {
|
|
443
|
-
create: ['kittens'],
|
|
444
|
-
})
|
|
445
|
-
await emitLabelEvent({
|
|
446
|
-
negateLabelVals: ['kittens'],
|
|
447
|
-
createLabelVals: [],
|
|
448
|
-
subject: bobsPostSubject,
|
|
449
|
-
})
|
|
450
|
-
await expect(getRecordLabels(post.uriStr)).resolves.toEqual([])
|
|
451
|
-
|
|
452
|
-
await emitLabelEvent({
|
|
453
|
-
createLabelVals: ['kittens'],
|
|
454
|
-
negateLabelVals: [],
|
|
455
|
-
subject: bobsPostSubject,
|
|
456
|
-
})
|
|
457
|
-
await expect(getRecordLabels(post.uriStr)).resolves.toEqual(['kittens'])
|
|
458
|
-
// Cleanup
|
|
459
|
-
await modService.formatAndCreateLabels(post.uriStr, post.cidStr, {
|
|
460
|
-
negate: ['kittens'],
|
|
461
|
-
})
|
|
462
|
-
})
|
|
463
|
-
|
|
464
|
-
it('no-ops when negating an already-negated label and reverses.', async () => {
|
|
465
|
-
const { ctx } = ozone
|
|
466
|
-
const post = sc.posts[sc.dids.bob][0].ref
|
|
467
|
-
const modService = ctx.modService(ctx.db)
|
|
468
|
-
await emitLabelEvent({
|
|
469
|
-
negateLabelVals: ['bears'],
|
|
470
|
-
createLabelVals: [],
|
|
471
|
-
subject: {
|
|
472
|
-
$type: 'com.atproto.repo.strongRef',
|
|
473
|
-
uri: post.uriStr,
|
|
474
|
-
cid: post.cidStr,
|
|
475
|
-
},
|
|
476
|
-
})
|
|
477
|
-
await expect(getRecordLabels(post.uriStr)).resolves.toEqual([])
|
|
478
|
-
await emitLabelEvent({
|
|
479
|
-
createLabelVals: ['bears'],
|
|
480
|
-
negateLabelVals: [],
|
|
481
|
-
subject: {
|
|
482
|
-
$type: 'com.atproto.repo.strongRef',
|
|
483
|
-
uri: post.uriStr,
|
|
484
|
-
cid: post.cidStr,
|
|
485
|
-
},
|
|
486
|
-
})
|
|
487
|
-
await expect(getRecordLabels(post.uriStr)).resolves.toEqual(['bears'])
|
|
488
|
-
// Cleanup
|
|
489
|
-
await modService.formatAndCreateLabels(post.uriStr, post.cidStr, {
|
|
490
|
-
negate: ['bears'],
|
|
491
|
-
})
|
|
492
|
-
})
|
|
493
|
-
|
|
494
|
-
it('creates non-existing labels and reverses.', async () => {
|
|
495
|
-
const post = sc.posts[sc.dids.bob][0].ref
|
|
496
|
-
await emitLabelEvent({
|
|
497
|
-
createLabelVals: ['puppies', 'doggies'],
|
|
498
|
-
negateLabelVals: [],
|
|
499
|
-
subject: {
|
|
500
|
-
$type: 'com.atproto.repo.strongRef',
|
|
501
|
-
uri: post.uriStr,
|
|
502
|
-
cid: post.cidStr,
|
|
503
|
-
},
|
|
504
|
-
})
|
|
505
|
-
await expect(getRecordLabels(post.uriStr)).resolves.toEqual([
|
|
506
|
-
'puppies',
|
|
507
|
-
'doggies',
|
|
508
|
-
])
|
|
509
|
-
await emitLabelEvent({
|
|
510
|
-
negateLabelVals: ['puppies', 'doggies'],
|
|
511
|
-
createLabelVals: [],
|
|
512
|
-
subject: {
|
|
513
|
-
$type: 'com.atproto.repo.strongRef',
|
|
514
|
-
uri: post.uriStr,
|
|
515
|
-
cid: post.cidStr,
|
|
516
|
-
},
|
|
517
|
-
})
|
|
518
|
-
await expect(getRecordLabels(post.uriStr)).resolves.toEqual([])
|
|
519
|
-
})
|
|
520
|
-
|
|
521
|
-
it('creates labels on a repo and reverses.', async () => {
|
|
522
|
-
await emitLabelEvent({
|
|
523
|
-
createLabelVals: ['puppies', 'doggies'],
|
|
524
|
-
negateLabelVals: [],
|
|
525
|
-
subject: {
|
|
526
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
527
|
-
did: sc.dids.bob,
|
|
528
|
-
},
|
|
529
|
-
})
|
|
530
|
-
await expect(getRepoLabels(sc.dids.bob)).resolves.toEqual([
|
|
531
|
-
'puppies',
|
|
532
|
-
'doggies',
|
|
533
|
-
])
|
|
534
|
-
await emitLabelEvent({
|
|
535
|
-
negateLabelVals: ['puppies', 'doggies'],
|
|
536
|
-
createLabelVals: [],
|
|
537
|
-
subject: {
|
|
538
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
539
|
-
did: sc.dids.bob,
|
|
540
|
-
},
|
|
541
|
-
})
|
|
542
|
-
await expect(getRepoLabels(sc.dids.bob)).resolves.toEqual([])
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
it('creates and negates labels on a repo and reverses.', async () => {
|
|
546
|
-
const { ctx } = ozone
|
|
547
|
-
const modService = ctx.modService(ctx.db)
|
|
548
|
-
await modService.formatAndCreateLabels(sc.dids.bob, null, {
|
|
549
|
-
create: ['kittens'],
|
|
550
|
-
})
|
|
551
|
-
await emitLabelEvent({
|
|
552
|
-
createLabelVals: ['puppies'],
|
|
553
|
-
negateLabelVals: ['kittens'],
|
|
554
|
-
subject: {
|
|
555
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
556
|
-
did: sc.dids.bob,
|
|
557
|
-
},
|
|
558
|
-
})
|
|
559
|
-
await expect(getRepoLabels(sc.dids.bob)).resolves.toEqual(['puppies'])
|
|
560
|
-
|
|
561
|
-
await emitLabelEvent({
|
|
562
|
-
negateLabelVals: ['puppies'],
|
|
563
|
-
createLabelVals: ['kittens'],
|
|
564
|
-
subject: {
|
|
565
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
566
|
-
did: sc.dids.bob,
|
|
567
|
-
},
|
|
568
|
-
})
|
|
569
|
-
await expect(getRepoLabels(sc.dids.bob)).resolves.toEqual(['kittens'])
|
|
570
|
-
})
|
|
571
|
-
|
|
572
|
-
it('creates expiring label', async () => {
|
|
573
|
-
await emitLabelEvent({
|
|
574
|
-
createLabelVals: ['temp'],
|
|
575
|
-
negateLabelVals: [],
|
|
576
|
-
subject: {
|
|
577
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
578
|
-
did: sc.dids.bob,
|
|
579
|
-
},
|
|
580
|
-
durationInHours: 24,
|
|
581
|
-
})
|
|
582
|
-
const repo = await getRepo(sc.dids.bob)
|
|
583
|
-
// Losely check that the expiry date is set to above 23 hours from now
|
|
584
|
-
expect(
|
|
585
|
-
`${repo?.labels?.[0].exp}` >
|
|
586
|
-
new Date(Date.now() + 23 * HOUR).toISOString(),
|
|
587
|
-
).toBeTruthy()
|
|
588
|
-
})
|
|
589
|
-
|
|
590
|
-
it('does not allow triage moderators to label.', async () => {
|
|
591
|
-
const attemptLabel = modClient.emitEvent(
|
|
592
|
-
{
|
|
593
|
-
event: {
|
|
594
|
-
$type: 'tools.ozone.moderation.defs#modEventLabel',
|
|
595
|
-
negateLabelVals: ['a'],
|
|
596
|
-
createLabelVals: ['b', 'c'],
|
|
597
|
-
},
|
|
598
|
-
createdBy: 'did:example:moderator',
|
|
599
|
-
reason: 'Y',
|
|
600
|
-
subject: {
|
|
601
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
602
|
-
did: sc.dids.bob,
|
|
603
|
-
},
|
|
604
|
-
},
|
|
605
|
-
'triage',
|
|
606
|
-
)
|
|
607
|
-
await expect(attemptLabel).rejects.toThrow(
|
|
608
|
-
'Must be a full moderator to label content',
|
|
609
|
-
)
|
|
610
|
-
})
|
|
611
|
-
|
|
612
|
-
it('does not allow take down event on takendown post or reverse takedown on available post.', async () => {
|
|
613
|
-
await modClient.performTakedown({
|
|
614
|
-
subject: repoSubject(sc.dids.bob),
|
|
615
|
-
})
|
|
616
|
-
await expect(
|
|
617
|
-
modClient.performTakedown({
|
|
618
|
-
subject: repoSubject(sc.dids.bob),
|
|
619
|
-
}),
|
|
620
|
-
).rejects.toThrow('Subject is already taken down')
|
|
621
|
-
|
|
622
|
-
// Cleanup
|
|
623
|
-
await modClient.performReverseTakedown({
|
|
624
|
-
subject: repoSubject(sc.dids.bob),
|
|
625
|
-
})
|
|
626
|
-
await expect(
|
|
627
|
-
modClient.performReverseTakedown({
|
|
628
|
-
subject: repoSubject(sc.dids.bob),
|
|
629
|
-
}),
|
|
630
|
-
).rejects.toThrow('Subject is not taken down')
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
it('fans out repo takedowns', async () => {
|
|
634
|
-
await modClient.performTakedown({
|
|
635
|
-
subject: repoSubject(sc.dids.bob),
|
|
636
|
-
})
|
|
637
|
-
await ozone.processAll()
|
|
638
|
-
|
|
639
|
-
const pdsRes1 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
|
|
640
|
-
{
|
|
641
|
-
did: sc.dids.bob,
|
|
642
|
-
},
|
|
643
|
-
{ headers: network.pds.adminAuthHeaders() },
|
|
644
|
-
)
|
|
645
|
-
expect(pdsRes1.data.takedown?.applied).toBe(true)
|
|
646
|
-
|
|
647
|
-
const bskyRes1 = await bskyAgent.api.com.atproto.admin.getSubjectStatus(
|
|
648
|
-
{
|
|
649
|
-
did: sc.dids.bob,
|
|
650
|
-
},
|
|
651
|
-
{ headers: network.pds.adminAuthHeaders() },
|
|
652
|
-
)
|
|
653
|
-
expect(bskyRes1.data.takedown?.applied).toBe(true)
|
|
654
|
-
|
|
655
|
-
const takedownLabel1 = await getLabel(sc.dids.bob, TAKEDOWN_LABEL)
|
|
656
|
-
expect(takedownLabel1).toBeDefined()
|
|
657
|
-
|
|
658
|
-
// cleanup
|
|
659
|
-
await modClient.performReverseTakedown({
|
|
660
|
-
subject: repoSubject(sc.dids.bob),
|
|
661
|
-
})
|
|
662
|
-
await ozone.processAll()
|
|
663
|
-
|
|
664
|
-
const pdsRes2 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
|
|
665
|
-
{
|
|
666
|
-
did: sc.dids.bob,
|
|
667
|
-
},
|
|
668
|
-
{ headers: network.pds.adminAuthHeaders() },
|
|
669
|
-
)
|
|
670
|
-
expect(pdsRes2.data.takedown?.applied).toBe(false)
|
|
671
|
-
|
|
672
|
-
const bskyRes2 = await bskyAgent.api.com.atproto.admin.getSubjectStatus(
|
|
673
|
-
{
|
|
674
|
-
did: sc.dids.bob,
|
|
675
|
-
},
|
|
676
|
-
{ headers: network.bsky.adminAuthHeaders() },
|
|
677
|
-
)
|
|
678
|
-
expect(bskyRes2.data.takedown?.applied).toBe(false)
|
|
679
|
-
|
|
680
|
-
const takedownLabel2 = await getLabel(sc.dids.bob, TAKEDOWN_LABEL)
|
|
681
|
-
expect(takedownLabel2).toBeUndefined()
|
|
682
|
-
})
|
|
683
|
-
|
|
684
|
-
it('allows full moderators to takedown.', async () => {
|
|
685
|
-
await modClient.emitEvent(
|
|
686
|
-
{
|
|
687
|
-
event: {
|
|
688
|
-
$type: 'tools.ozone.moderation.defs#modEventTakedown',
|
|
689
|
-
},
|
|
690
|
-
createdBy: 'did:example:moderator',
|
|
691
|
-
subject: {
|
|
692
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
693
|
-
did: sc.dids.bob,
|
|
694
|
-
},
|
|
695
|
-
},
|
|
696
|
-
'moderator',
|
|
697
|
-
)
|
|
698
|
-
// cleanup
|
|
699
|
-
await reverse({
|
|
700
|
-
subject: {
|
|
701
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
702
|
-
did: sc.dids.bob,
|
|
703
|
-
},
|
|
704
|
-
})
|
|
705
|
-
})
|
|
706
|
-
|
|
707
|
-
it('does not allow non-full moderators to takedown.', async () => {
|
|
708
|
-
const attemptTakedownTriage = modClient.emitEvent(
|
|
709
|
-
{
|
|
710
|
-
event: {
|
|
711
|
-
$type: 'tools.ozone.moderation.defs#modEventTakedown',
|
|
712
|
-
},
|
|
713
|
-
createdBy: 'did:example:moderator',
|
|
714
|
-
subject: {
|
|
715
|
-
$type: 'com.atproto.admin.defs#repoRef',
|
|
716
|
-
did: sc.dids.bob,
|
|
717
|
-
},
|
|
718
|
-
},
|
|
719
|
-
'triage',
|
|
720
|
-
)
|
|
721
|
-
await expect(attemptTakedownTriage).rejects.toThrow(
|
|
722
|
-
'Must be a full moderator to take this type of action',
|
|
723
|
-
)
|
|
724
|
-
})
|
|
725
|
-
|
|
726
|
-
it('automatically reverses actions marked with duration', async () => {
|
|
727
|
-
await sc.createReport({
|
|
728
|
-
reasonType: REASONSPAM,
|
|
729
|
-
subject: repoSubject(sc.dids.bob),
|
|
730
|
-
reportedBy: sc.dids.alice,
|
|
731
|
-
})
|
|
732
|
-
const action = await modClient.performTakedown({
|
|
733
|
-
subject: repoSubject(sc.dids.bob),
|
|
734
|
-
// Use negative value to set the expiry time in the past so that the action is automatically reversed
|
|
735
|
-
// right away without having to wait n number of hours for a successful assertion
|
|
736
|
-
durationInHours: -1,
|
|
737
|
-
})
|
|
738
|
-
await ozone.processAll()
|
|
739
|
-
|
|
740
|
-
const statusesAfterTakedown = await modClient.queryStatuses(
|
|
741
|
-
{ subject: sc.dids.bob },
|
|
742
|
-
'moderator',
|
|
743
|
-
)
|
|
744
|
-
|
|
745
|
-
expect(statusesAfterTakedown.subjectStatuses[0]).toMatchObject({
|
|
746
|
-
takendown: true,
|
|
747
|
-
})
|
|
748
|
-
|
|
749
|
-
// In the actual app, this will be instantiated and run on server startup
|
|
750
|
-
const reverser = new EventReverser(
|
|
751
|
-
network.ozone.ctx.db,
|
|
752
|
-
// @ts-expect-error Error due to circular dependency with the dev-env package
|
|
753
|
-
network.ozone.ctx.modService,
|
|
754
|
-
)
|
|
755
|
-
await reverser.findAndRevertDueActions()
|
|
756
|
-
await ozone.processAll()
|
|
757
|
-
|
|
758
|
-
const [eventList, statuses] = await Promise.all([
|
|
759
|
-
modClient.queryEvents({ subject: sc.dids.bob }, 'moderator'),
|
|
760
|
-
modClient.queryStatuses({ subject: sc.dids.bob }, 'moderator'),
|
|
761
|
-
])
|
|
762
|
-
|
|
763
|
-
expect(statuses.subjectStatuses[0]).toMatchObject({
|
|
764
|
-
takendown: false,
|
|
765
|
-
reviewState: REVIEWCLOSED,
|
|
766
|
-
})
|
|
767
|
-
// Verify that the automatic reversal is attributed to the original moderator of the temporary action
|
|
768
|
-
// and that the reason is set to indicate that the action was automatically reversed.
|
|
769
|
-
expect(eventList.events[0]).toMatchObject({
|
|
770
|
-
createdBy: action.createdBy,
|
|
771
|
-
event: {
|
|
772
|
-
$type: 'tools.ozone.moderation.defs#modEventReverseTakedown',
|
|
773
|
-
comment:
|
|
774
|
-
'[SCHEDULED_REVERSAL] Reverting action as originally scheduled',
|
|
775
|
-
},
|
|
776
|
-
})
|
|
777
|
-
})
|
|
778
|
-
|
|
779
|
-
it('allows conversation escalate', async () => {
|
|
780
|
-
const subject = {
|
|
781
|
-
$type: 'chat.bsky.convo.defs#convoRef',
|
|
782
|
-
did: sc.dids.bob,
|
|
783
|
-
convoId: '123',
|
|
784
|
-
}
|
|
785
|
-
await modClient.emitEvent({
|
|
786
|
-
event: {
|
|
787
|
-
$type: 'tools.ozone.moderation.defs#modEventEscalate',
|
|
788
|
-
comment: 'Y',
|
|
789
|
-
},
|
|
790
|
-
subject,
|
|
791
|
-
createdBy: 'did:example:admin',
|
|
792
|
-
})
|
|
793
|
-
|
|
794
|
-
const status = await network.ozone.ctx.db.db
|
|
795
|
-
.selectFrom('moderation_subject_status')
|
|
796
|
-
.selectAll()
|
|
797
|
-
.where('did', '=', subject.did)
|
|
798
|
-
.where('recordPath', '=', '')
|
|
799
|
-
.where('convoId', '=', subject.convoId)
|
|
800
|
-
.executeTakeFirst()
|
|
801
|
-
|
|
802
|
-
expect(status?.reviewState).toEqual(REVIEWESCALATED)
|
|
803
|
-
})
|
|
804
|
-
|
|
805
|
-
async function emitLabelEvent(
|
|
806
|
-
opts: Partial<ToolsOzoneModerationEmitEvent.InputSchema> & {
|
|
807
|
-
subject: ToolsOzoneModerationEmitEvent.InputSchema['subject']
|
|
808
|
-
createLabelVals: ModEventLabel['createLabelVals']
|
|
809
|
-
negateLabelVals: ModEventLabel['negateLabelVals']
|
|
810
|
-
durationInHours?: ModEventLabel['durationInHours']
|
|
811
|
-
},
|
|
812
|
-
) {
|
|
813
|
-
const { createLabelVals, negateLabelVals, durationInHours } = opts
|
|
814
|
-
const event = await modClient.emitEvent({
|
|
815
|
-
event: {
|
|
816
|
-
$type: 'tools.ozone.moderation.defs#modEventLabel',
|
|
817
|
-
createLabelVals,
|
|
818
|
-
negateLabelVals,
|
|
819
|
-
durationInHours,
|
|
820
|
-
},
|
|
821
|
-
createdBy: 'did:example:admin',
|
|
822
|
-
reason: 'Y',
|
|
823
|
-
...opts,
|
|
824
|
-
})
|
|
825
|
-
return event
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
async function reverse(
|
|
829
|
-
opts: Partial<ToolsOzoneModerationEmitEvent.InputSchema> & {
|
|
830
|
-
subject: ToolsOzoneModerationEmitEvent.InputSchema['subject']
|
|
831
|
-
},
|
|
832
|
-
) {
|
|
833
|
-
await modClient.emitEvent({
|
|
834
|
-
event: {
|
|
835
|
-
$type: 'tools.ozone.moderation.defs#modEventReverseTakedown',
|
|
836
|
-
},
|
|
837
|
-
createdBy: 'did:example:admin',
|
|
838
|
-
reason: 'Y',
|
|
839
|
-
...opts,
|
|
840
|
-
})
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
async function getRecordLabels(uri: string) {
|
|
844
|
-
const result = await agent.tools.ozone.moderation.getRecord(
|
|
845
|
-
{ uri },
|
|
846
|
-
{
|
|
847
|
-
headers: await network.ozone.modHeaders(
|
|
848
|
-
ids.ToolsOzoneModerationGetRecord,
|
|
849
|
-
),
|
|
850
|
-
},
|
|
851
|
-
)
|
|
852
|
-
const labels = result.data.labels ?? []
|
|
853
|
-
return labels.map((l) => l.val)
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
async function getRepo(did: string) {
|
|
857
|
-
const result = await agent.tools.ozone.moderation.getRepo(
|
|
858
|
-
{ did },
|
|
859
|
-
{
|
|
860
|
-
headers: await network.ozone.modHeaders(
|
|
861
|
-
ids.ToolsOzoneModerationGetRepo,
|
|
862
|
-
),
|
|
863
|
-
},
|
|
864
|
-
)
|
|
865
|
-
return result.data
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
async function getRepoLabels(did: string) {
|
|
869
|
-
const result = await getRepo(did)
|
|
870
|
-
const labels = result.labels ?? []
|
|
871
|
-
return labels.map((l) => l.val)
|
|
872
|
-
}
|
|
873
|
-
})
|
|
874
|
-
|
|
875
|
-
describe('blob takedown', () => {
|
|
876
|
-
let post: { ref: RecordRef; images: ImageRef[] }
|
|
877
|
-
let blob: ImageRef
|
|
878
|
-
let imageUri: string
|
|
879
|
-
beforeAll(async () => {
|
|
880
|
-
const { ctx } = network.bsky
|
|
881
|
-
post = sc.posts[sc.dids.carol][0]
|
|
882
|
-
blob = post.images[1]
|
|
883
|
-
imageUri = ctx.views.imgUriBuilder
|
|
884
|
-
.getPresetUri(
|
|
885
|
-
'feed_thumbnail',
|
|
886
|
-
sc.dids.carol,
|
|
887
|
-
blob.image.ref.toString(),
|
|
888
|
-
)
|
|
889
|
-
.replace(ctx.cfg.publicUrl || '', network.bsky.url)
|
|
890
|
-
// Warm image server cache
|
|
891
|
-
await fetch(imageUri)
|
|
892
|
-
const cached = await fetch(imageUri)
|
|
893
|
-
expect(cached.headers.get('x-cache')).toEqual('hit')
|
|
894
|
-
await modClient.performTakedown({
|
|
895
|
-
subject: recordSubject(post.ref),
|
|
896
|
-
subjectBlobCids: [blob.image.ref.toString()],
|
|
897
|
-
})
|
|
898
|
-
await ozone.processAll()
|
|
899
|
-
})
|
|
900
|
-
|
|
901
|
-
it('sets blobCids in moderation status', async () => {
|
|
902
|
-
const { subjectStatuses } = await modClient.queryStatuses({
|
|
903
|
-
subject: post.ref.uriStr,
|
|
904
|
-
})
|
|
905
|
-
|
|
906
|
-
expect(subjectStatuses[0].subjectBlobCids).toEqual([
|
|
907
|
-
blob.image.ref.toString(),
|
|
908
|
-
])
|
|
909
|
-
})
|
|
910
|
-
|
|
911
|
-
it('prevents resolution of blob', async () => {
|
|
912
|
-
const blobPath = `/blob/${sc.dids.carol}/${blob.image.ref.toString()}`
|
|
913
|
-
const resolveBlob = await fetch(`${network.bsky.url}${blobPath}`)
|
|
914
|
-
expect(resolveBlob.status).toEqual(404)
|
|
915
|
-
expect(await resolveBlob.json()).toEqual({
|
|
916
|
-
error: 'NotFoundError',
|
|
917
|
-
message: 'Blob not found',
|
|
918
|
-
})
|
|
919
|
-
})
|
|
920
|
-
|
|
921
|
-
// @TODO add back in with image invalidation, see bluesky-social/atproto#2087
|
|
922
|
-
it.skip('prevents image blob from being served, even when cached.', async () => {
|
|
923
|
-
const fetchImage = await fetch(imageUri)
|
|
924
|
-
expect(fetchImage.status).toEqual(404)
|
|
925
|
-
expect(await fetchImage.json()).toMatchObject({
|
|
926
|
-
message: 'Blob not found',
|
|
927
|
-
})
|
|
928
|
-
})
|
|
929
|
-
|
|
930
|
-
it('invalidates the image in the cdn', async () => {
|
|
931
|
-
const blobCid = blob.image.ref.toString()
|
|
932
|
-
expect(mockInvalidator.invalidated.length).toBe(1)
|
|
933
|
-
expect(mockInvalidator.invalidated.at(0)?.subject).toBe(blobCid)
|
|
934
|
-
expect(mockInvalidator.invalidated.at(0)?.paths.at(0)).toEqual(
|
|
935
|
-
`/path1/${sc.dids.carol}/${blobCid}`,
|
|
936
|
-
)
|
|
937
|
-
expect(mockInvalidator.invalidated.at(0)?.paths.at(1)).toEqual(
|
|
938
|
-
`/path2/${sc.dids.carol}/${blobCid}`,
|
|
939
|
-
)
|
|
940
|
-
})
|
|
941
|
-
|
|
942
|
-
it('fans takedown out to pds', async () => {
|
|
943
|
-
const res = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
|
|
944
|
-
{
|
|
945
|
-
did: sc.dids.carol,
|
|
946
|
-
blob: blob.image.ref.toString(),
|
|
947
|
-
},
|
|
948
|
-
{ headers: network.pds.adminAuthHeaders() },
|
|
949
|
-
)
|
|
950
|
-
expect(res.data.takedown?.applied).toBe(true)
|
|
951
|
-
})
|
|
952
|
-
|
|
953
|
-
it('restores blob when action is reversed.', async () => {
|
|
954
|
-
await modClient.performReverseTakedown({
|
|
955
|
-
subject: recordSubject(post.ref),
|
|
956
|
-
subjectBlobCids: [blob.image.ref.toString()],
|
|
957
|
-
})
|
|
958
|
-
|
|
959
|
-
await ozone.processAll()
|
|
960
|
-
|
|
961
|
-
// Can resolve blob
|
|
962
|
-
const blobPath = `/blob/${sc.dids.carol}/${blob.image.ref.toString()}`
|
|
963
|
-
const resolveBlob = await fetch(`${network.bsky.url}${blobPath}`)
|
|
964
|
-
expect(resolveBlob.status).toEqual(200)
|
|
965
|
-
|
|
966
|
-
// Can fetch through image server
|
|
967
|
-
const fetchImage = await fetch(imageUri)
|
|
968
|
-
expect(fetchImage.status).toEqual(200)
|
|
969
|
-
const size = Number(fetchImage.headers.get('content-length'))
|
|
970
|
-
expect(size).toBeGreaterThan(9000)
|
|
971
|
-
})
|
|
972
|
-
|
|
973
|
-
it('fans reversal out to pds', async () => {
|
|
974
|
-
const res = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
|
|
975
|
-
{
|
|
976
|
-
did: sc.dids.carol,
|
|
977
|
-
blob: blob.image.ref.toString(),
|
|
978
|
-
},
|
|
979
|
-
{ headers: network.pds.adminAuthHeaders() },
|
|
980
|
-
)
|
|
981
|
-
expect(res.data.takedown?.applied).toBe(false)
|
|
982
|
-
})
|
|
983
|
-
})
|
|
984
|
-
})
|
|
985
|
-
|
|
986
|
-
class MockInvalidator implements ImageInvalidator {
|
|
987
|
-
invalidated: { subject: string; paths: string[] }[] = []
|
|
988
|
-
|
|
989
|
-
async invalidate(subject: string, paths: string[]) {
|
|
990
|
-
this.invalidated.push({ subject, paths })
|
|
991
|
-
}
|
|
992
|
-
}
|