@atcute/bluesky-moderation 2.0.4 → 3.0.0
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/README.md +126 -128
- package/dist/_test-util/mock.d.ts +2735 -0
- package/dist/_test-util/mock.d.ts.map +1 -0
- package/dist/_test-util/mock.js +109 -0
- package/dist/_test-util/mock.js.map +1 -0
- package/dist/_test-util/moderation-behavior.d.ts +57 -0
- package/dist/_test-util/moderation-behavior.d.ts.map +1 -0
- package/dist/_test-util/moderation-behavior.js +158 -0
- package/dist/_test-util/moderation-behavior.js.map +1 -0
- package/dist/behaviors.d.ts +21 -18
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +18 -21
- package/dist/behaviors.js.map +1 -1
- package/dist/decision.d.ts +25 -24
- package/dist/decision.d.ts.map +1 -1
- package/dist/decision.js +14 -16
- package/dist/decision.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/internal/keyword-filter.d.ts +3 -3
- package/dist/internal/keyword-filter.d.ts.map +1 -1
- package/dist/internal/keyword-filter.js.map +1 -1
- package/dist/keyword-filter.d.ts +7 -6
- package/dist/keyword-filter.d.ts.map +1 -1
- package/dist/keyword-filter.js +8 -7
- package/dist/keyword-filter.js.map +1 -1
- package/dist/label.d.ts +30 -27
- package/dist/label.d.ts.map +1 -1
- package/dist/label.js +24 -28
- package/dist/label.js.map +1 -1
- package/dist/subjects/feed-generator.d.ts +3 -3
- package/dist/subjects/feed-generator.d.ts.map +1 -1
- package/dist/subjects/feed-generator.js.map +1 -1
- package/dist/subjects/list.d.ts +2 -2
- package/dist/subjects/list.d.ts.map +1 -1
- package/dist/subjects/list.js.map +1 -1
- package/dist/subjects/notification.d.ts +3 -3
- package/dist/subjects/notification.d.ts.map +1 -1
- package/dist/subjects/notification.js.map +1 -1
- package/dist/subjects/post.d.ts +4 -3
- package/dist/subjects/post.d.ts.map +1 -1
- package/dist/subjects/post.js.map +1 -1
- package/dist/subjects/profile.d.ts +2 -2
- package/dist/subjects/profile.d.ts.map +1 -1
- package/dist/subjects/profile.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/ui.d.ts +2 -2
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +4 -4
- package/dist/ui.js.map +1 -1
- package/lib/_test-util/mock.ts +214 -0
- package/lib/_test-util/moderation-behavior.ts +259 -0
- package/lib/behaviors.ts +21 -18
- package/lib/decision.ts +26 -26
- package/lib/index.ts +11 -11
- package/lib/internal/keyword-filter.ts +1 -1
- package/lib/keyword-filter.ts +9 -6
- package/lib/label.ts +32 -28
- package/lib/subjects/feed-generator.ts +4 -4
- package/lib/subjects/list.ts +4 -4
- package/lib/subjects/notification.ts +4 -4
- package/lib/subjects/post.ts +6 -7
- package/lib/subjects/profile.ts +3 -3
- package/lib/types.ts +2 -2
- package/lib/ui.ts +7 -7
- package/package.json +14 -12
package/dist/ui.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DisplayContext } from './behaviors.
|
|
2
|
-
import { type ModerationCause, type ModerationDecision } from './decision.
|
|
1
|
+
import { DisplayContext } from './behaviors.ts';
|
|
2
|
+
import { type ModerationCause, type ModerationDecision } from './decision.ts';
|
|
3
3
|
export interface DisplayRestrictions {
|
|
4
4
|
noOverride: boolean;
|
|
5
5
|
filters: ModerationCause[];
|
package/dist/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,cAAc,EAMd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnG,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,sBAAsB,
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,cAAc,EAMd,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAuB,KAAK,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnG,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,eAAO,MAAM,sBAAsB,gFA8JlC,CAAC"}
|
package/dist/ui.js
CHANGED
|
@@ -133,10 +133,10 @@ export const getDisplayRestrictions = (decision, context) => {
|
|
|
133
133
|
}
|
|
134
134
|
return {
|
|
135
135
|
noOverride,
|
|
136
|
-
filters: filters.sort(sortByPriority),
|
|
137
|
-
blurs: blurs.sort(sortByPriority),
|
|
138
|
-
alerts: alerts.sort(sortByPriority),
|
|
139
|
-
informs: informs.sort(sortByPriority),
|
|
136
|
+
filters: filters.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
|
|
137
|
+
blurs: blurs.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
|
|
138
|
+
alerts: alerts.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
|
|
139
|
+
informs: informs.sort(sortByPriority), // oxlint-disable-line unicorn/no-array-sort -- local array
|
|
140
140
|
};
|
|
141
141
|
};
|
|
142
142
|
const sortByPriority = (a, b) => {
|
package/dist/ui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,cAAc,EACd,cAAc,EACd,aAAa,EACb,qBAAqB,EACrB,WAAW,EACX,gBAAgB,EAChB,aAAa,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAiD,MAAM,eAAe,CAAC;AACnG,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAU7C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACrC,QAA4B,EAC5B,OAAuB,EACD,EAAE;
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../lib/ui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { ComAtprotoLabelDefs } from '@atcute/atproto';
|
|
2
|
+
import type {
|
|
3
|
+
AppBskyActorDefs,
|
|
4
|
+
AppBskyEmbedRecord,
|
|
5
|
+
AppBskyFeedDefs,
|
|
6
|
+
AppBskyFeedPost,
|
|
7
|
+
AppBskyGraphDefs,
|
|
8
|
+
AppBskyNotificationListNotifications,
|
|
9
|
+
} from '@atcute/bluesky';
|
|
10
|
+
import type { $type, Did, GenericUri, Handle, ResourceUri } from '@atcute/lexicons';
|
|
11
|
+
|
|
12
|
+
const FAKE_CID = 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq';
|
|
13
|
+
|
|
14
|
+
export const post = ({
|
|
15
|
+
text,
|
|
16
|
+
facets,
|
|
17
|
+
reply,
|
|
18
|
+
embed,
|
|
19
|
+
}: {
|
|
20
|
+
text: string;
|
|
21
|
+
facets?: AppBskyFeedPost.Main['facets'];
|
|
22
|
+
reply?: AppBskyFeedPost.ReplyRef;
|
|
23
|
+
embed?: AppBskyFeedPost.Main['embed'];
|
|
24
|
+
}): AppBskyFeedPost.Main => {
|
|
25
|
+
return {
|
|
26
|
+
$type: 'app.bsky.feed.post',
|
|
27
|
+
text,
|
|
28
|
+
facets,
|
|
29
|
+
reply,
|
|
30
|
+
embed,
|
|
31
|
+
langs: ['en'],
|
|
32
|
+
createdAt: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const postView = ({
|
|
37
|
+
record,
|
|
38
|
+
author,
|
|
39
|
+
embed,
|
|
40
|
+
replyCount,
|
|
41
|
+
repostCount,
|
|
42
|
+
likeCount,
|
|
43
|
+
viewer,
|
|
44
|
+
labels,
|
|
45
|
+
}: {
|
|
46
|
+
record: AppBskyFeedPost.Main;
|
|
47
|
+
author: AppBskyActorDefs.ProfileViewBasic;
|
|
48
|
+
embed?: AppBskyFeedDefs.PostView['embed'];
|
|
49
|
+
replyCount?: number;
|
|
50
|
+
repostCount?: number;
|
|
51
|
+
likeCount?: number;
|
|
52
|
+
viewer?: AppBskyFeedDefs.ViewerState;
|
|
53
|
+
labels?: ComAtprotoLabelDefs.Label[];
|
|
54
|
+
}): $type.enforce<AppBskyFeedDefs.PostView> => {
|
|
55
|
+
return {
|
|
56
|
+
$type: 'app.bsky.feed.defs#postView',
|
|
57
|
+
uri: `at://${author.did}/app.bsky.feed.post/fake`,
|
|
58
|
+
cid: FAKE_CID,
|
|
59
|
+
author,
|
|
60
|
+
record,
|
|
61
|
+
embed,
|
|
62
|
+
replyCount,
|
|
63
|
+
repostCount,
|
|
64
|
+
likeCount,
|
|
65
|
+
indexedAt: new Date().toISOString(),
|
|
66
|
+
viewer,
|
|
67
|
+
labels,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const embedRecordView = ({
|
|
72
|
+
record,
|
|
73
|
+
author,
|
|
74
|
+
labels,
|
|
75
|
+
}: {
|
|
76
|
+
record: AppBskyFeedPost.Main;
|
|
77
|
+
author: AppBskyActorDefs.ProfileViewBasic;
|
|
78
|
+
labels?: ComAtprotoLabelDefs.Label[];
|
|
79
|
+
}): $type.enforce<AppBskyEmbedRecord.View> => {
|
|
80
|
+
return {
|
|
81
|
+
$type: 'app.bsky.embed.record#view',
|
|
82
|
+
record: {
|
|
83
|
+
$type: 'app.bsky.embed.record#viewRecord',
|
|
84
|
+
uri: `at://${author.did}/app.bsky.feed.post/fake`,
|
|
85
|
+
cid: FAKE_CID,
|
|
86
|
+
author,
|
|
87
|
+
value: record,
|
|
88
|
+
labels,
|
|
89
|
+
indexedAt: new Date().toISOString(),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const profileView = ({
|
|
95
|
+
handle,
|
|
96
|
+
displayName,
|
|
97
|
+
viewer,
|
|
98
|
+
labels,
|
|
99
|
+
}: {
|
|
100
|
+
handle: Handle;
|
|
101
|
+
displayName?: string;
|
|
102
|
+
viewer?: AppBskyActorDefs.ViewerState;
|
|
103
|
+
labels?: ComAtprotoLabelDefs.Label[];
|
|
104
|
+
}): $type.omit<AppBskyActorDefs.ProfileView | AppBskyActorDefs.ProfileViewBasic> => {
|
|
105
|
+
return {
|
|
106
|
+
did: `did:web:${handle}`,
|
|
107
|
+
handle,
|
|
108
|
+
displayName,
|
|
109
|
+
viewer,
|
|
110
|
+
labels,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export const actorViewerState = ({
|
|
115
|
+
muted,
|
|
116
|
+
mutedByList,
|
|
117
|
+
blockedBy,
|
|
118
|
+
blocking,
|
|
119
|
+
blockingByList,
|
|
120
|
+
following,
|
|
121
|
+
followedBy,
|
|
122
|
+
}: {
|
|
123
|
+
muted?: boolean;
|
|
124
|
+
mutedByList?: AppBskyGraphDefs.ListViewBasic;
|
|
125
|
+
blockedBy?: boolean;
|
|
126
|
+
blocking?: ResourceUri;
|
|
127
|
+
blockingByList?: AppBskyGraphDefs.ListViewBasic;
|
|
128
|
+
following?: ResourceUri;
|
|
129
|
+
followedBy?: ResourceUri;
|
|
130
|
+
}): AppBskyActorDefs.ViewerState => {
|
|
131
|
+
return {
|
|
132
|
+
muted,
|
|
133
|
+
mutedByList,
|
|
134
|
+
blockedBy,
|
|
135
|
+
blocking,
|
|
136
|
+
blockingByList,
|
|
137
|
+
following,
|
|
138
|
+
followedBy,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const listViewBasic = ({ name }: { name: string }): AppBskyGraphDefs.ListViewBasic => {
|
|
143
|
+
return {
|
|
144
|
+
uri: 'at://did:plc:fake/app.bsky.graph.list/fake',
|
|
145
|
+
cid: FAKE_CID,
|
|
146
|
+
name,
|
|
147
|
+
purpose: 'app.bsky.graph.defs#modlist',
|
|
148
|
+
indexedAt: new Date().toISOString(),
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const replyNotification = ({
|
|
153
|
+
author,
|
|
154
|
+
record,
|
|
155
|
+
labels,
|
|
156
|
+
}: {
|
|
157
|
+
record: AppBskyFeedPost.Main;
|
|
158
|
+
author: AppBskyActorDefs.ProfileView;
|
|
159
|
+
labels?: ComAtprotoLabelDefs.Label[];
|
|
160
|
+
}): AppBskyNotificationListNotifications.Notification => {
|
|
161
|
+
return {
|
|
162
|
+
uri: `at://${author.did}/app.bsky.feed.post/fake`,
|
|
163
|
+
cid: FAKE_CID,
|
|
164
|
+
author,
|
|
165
|
+
reason: 'reply',
|
|
166
|
+
reasonSubject: `at://${author.did}/app.bsky.feed.post/fake-parent`,
|
|
167
|
+
record,
|
|
168
|
+
isRead: false,
|
|
169
|
+
indexedAt: new Date().toISOString(),
|
|
170
|
+
labels,
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const followNotification = ({
|
|
175
|
+
author,
|
|
176
|
+
subjectDid,
|
|
177
|
+
labels,
|
|
178
|
+
}: {
|
|
179
|
+
author: AppBskyActorDefs.ProfileView;
|
|
180
|
+
subjectDid: string;
|
|
181
|
+
labels?: ComAtprotoLabelDefs.Label[];
|
|
182
|
+
}): AppBskyNotificationListNotifications.Notification => {
|
|
183
|
+
return {
|
|
184
|
+
uri: `at://${author.did}/app.bsky.graph.follow/fake`,
|
|
185
|
+
cid: FAKE_CID,
|
|
186
|
+
author,
|
|
187
|
+
reason: 'follow',
|
|
188
|
+
record: {
|
|
189
|
+
$type: 'app.bsky.graph.follow',
|
|
190
|
+
createdAt: new Date().toISOString(),
|
|
191
|
+
subject: subjectDid,
|
|
192
|
+
},
|
|
193
|
+
isRead: false,
|
|
194
|
+
indexedAt: new Date().toISOString(),
|
|
195
|
+
labels,
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const label = ({
|
|
200
|
+
val,
|
|
201
|
+
uri,
|
|
202
|
+
src,
|
|
203
|
+
}: {
|
|
204
|
+
val: string;
|
|
205
|
+
uri: GenericUri;
|
|
206
|
+
src?: Did;
|
|
207
|
+
}): ComAtprotoLabelDefs.Label => {
|
|
208
|
+
return {
|
|
209
|
+
src: src || 'did:plc:fake-labeler',
|
|
210
|
+
uri,
|
|
211
|
+
val,
|
|
212
|
+
cts: new Date().toISOString(),
|
|
213
|
+
};
|
|
214
|
+
};
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type { ComAtprotoLabelDefs } from '@atcute/atproto';
|
|
2
|
+
|
|
3
|
+
import { expect } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { LabelPreference, type DisplayRestrictions, type ModerationOptions } from '../index.ts';
|
|
6
|
+
|
|
7
|
+
import * as m from './mock.ts';
|
|
8
|
+
|
|
9
|
+
export type ModerationTestSuiteResultFlag = 'filter' | 'blur' | 'alert' | 'inform' | 'noOverride';
|
|
10
|
+
|
|
11
|
+
export interface ModerationTestSuiteScenario {
|
|
12
|
+
cfg: string;
|
|
13
|
+
subject: 'post' | 'profile' | 'userlist' | 'feedgen';
|
|
14
|
+
author: string;
|
|
15
|
+
quoteAuthor?: string;
|
|
16
|
+
labels: {
|
|
17
|
+
post?: string[];
|
|
18
|
+
profile?: string[];
|
|
19
|
+
account?: string[];
|
|
20
|
+
quotedPost?: string[];
|
|
21
|
+
quotedAccount?: string[];
|
|
22
|
+
};
|
|
23
|
+
behaviors: {
|
|
24
|
+
profileList?: ModerationTestSuiteResultFlag[];
|
|
25
|
+
profileView?: ModerationTestSuiteResultFlag[];
|
|
26
|
+
profileMedia?: ModerationTestSuiteResultFlag[];
|
|
27
|
+
contentList?: ModerationTestSuiteResultFlag[];
|
|
28
|
+
contentView?: ModerationTestSuiteResultFlag[];
|
|
29
|
+
contentMedia?: ModerationTestSuiteResultFlag[];
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type SuiteUsers = Record<
|
|
34
|
+
string,
|
|
35
|
+
{
|
|
36
|
+
blocking: boolean;
|
|
37
|
+
blockingByList: boolean;
|
|
38
|
+
blockedBy: boolean;
|
|
39
|
+
muted: boolean;
|
|
40
|
+
mutedByList: boolean;
|
|
41
|
+
}
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
export type SuiteConfigurations = Record<
|
|
45
|
+
string,
|
|
46
|
+
{
|
|
47
|
+
authed?: boolean;
|
|
48
|
+
adultContentEnabled?: boolean;
|
|
49
|
+
settings?: Record<string, LabelPreference>;
|
|
50
|
+
}
|
|
51
|
+
>;
|
|
52
|
+
|
|
53
|
+
export type SuiteScenarios = Record<string, ModerationTestSuiteScenario>;
|
|
54
|
+
|
|
55
|
+
expect.extend({
|
|
56
|
+
toBeModerationResult(
|
|
57
|
+
actual: DisplayRestrictions,
|
|
58
|
+
expected: ModerationTestSuiteResultFlag[] | undefined,
|
|
59
|
+
context = '',
|
|
60
|
+
stringifiedResult: string | undefined = undefined,
|
|
61
|
+
_ignoreCause = false,
|
|
62
|
+
) {
|
|
63
|
+
const fail = (msg: string) => ({
|
|
64
|
+
pass: false,
|
|
65
|
+
message: () => `${msg}.${stringifiedResult ? ` Full result: ${stringifiedResult}` : ''}`,
|
|
66
|
+
});
|
|
67
|
+
// let cause = actual.causes?.type as string
|
|
68
|
+
// if (actual.cause?.type === 'label') {
|
|
69
|
+
// cause = `label:${actual.cause.labelDef.id}`
|
|
70
|
+
// } else if (actual.cause?.type === 'muted') {
|
|
71
|
+
// if (actual.cause.source.type === 'list') {
|
|
72
|
+
// cause = 'muted-by-list'
|
|
73
|
+
// }
|
|
74
|
+
// } else if (actual.cause?.type === 'blocking') {
|
|
75
|
+
// if (actual.cause.source.type === 'list') {
|
|
76
|
+
// cause = 'blocking-by-list'
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
if (!expected) {
|
|
80
|
+
// if (!ignoreCause && actual.cause) {
|
|
81
|
+
// return fail(`${context} expected to be a no-op, got ${cause}`)
|
|
82
|
+
// }
|
|
83
|
+
if (actual.informs.length > 0) {
|
|
84
|
+
return fail(`${context} expected to be a no-op, got inform=true`);
|
|
85
|
+
}
|
|
86
|
+
if (actual.alerts.length > 0) {
|
|
87
|
+
return fail(`${context} expected to be a no-op, got alert=true`);
|
|
88
|
+
}
|
|
89
|
+
if (actual.blurs.length > 0) {
|
|
90
|
+
return fail(`${context} expected to be a no-op, got blur=true`);
|
|
91
|
+
}
|
|
92
|
+
if (actual.filters.length > 0) {
|
|
93
|
+
return fail(`${context} expected to be a no-op, got filter=true`);
|
|
94
|
+
}
|
|
95
|
+
if (actual.noOverride) {
|
|
96
|
+
return fail(`${context} expected to be a no-op, got noOverride=true`);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// if (!ignoreCause && cause !== expected.cause) {
|
|
100
|
+
// return fail(`${context} expected to be ${expected.cause}, got ${cause}`)
|
|
101
|
+
// }
|
|
102
|
+
const expectedInform = expected.includes('inform');
|
|
103
|
+
if (!!(actual.informs.length > 0) !== expectedInform) {
|
|
104
|
+
return fail(
|
|
105
|
+
`${context} expected to be inform=${expectedInform}, got ${actual.informs.length > 0 || false}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const expectedAlert = expected.includes('alert');
|
|
109
|
+
if (!!(actual.alerts.length > 0) !== expectedAlert) {
|
|
110
|
+
return fail(
|
|
111
|
+
`${context} expected to be alert=${expectedAlert}, got ${actual.alerts.length > 0 || false}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
const expectedBlur = expected.includes('blur');
|
|
115
|
+
if (!!(actual.blurs.length > 0) !== expectedBlur) {
|
|
116
|
+
return fail(
|
|
117
|
+
`${context} expected to be blur=${expectedBlur}, got ${actual.blurs.length > 0 || false}`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const expectedFilter = expected.includes('filter');
|
|
121
|
+
if (!!(actual.filters.length > 0) !== expectedFilter) {
|
|
122
|
+
return fail(
|
|
123
|
+
`${context} expected to be filter=${expectedFilter}, got ${actual.filters.length > 0 || false}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const expectedNoOverride = expected.includes('noOverride');
|
|
127
|
+
if (!!actual.noOverride !== expectedNoOverride) {
|
|
128
|
+
return fail(
|
|
129
|
+
`${context} expected to be noOverride=${expectedNoOverride}, got ${actual.noOverride || false}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { pass: true, message: () => '' };
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
declare module 'vitest' {
|
|
138
|
+
// oxlint-disable-next-line no-unused-vars -- required for module augmentation
|
|
139
|
+
interface Assertion<T = any> {
|
|
140
|
+
toBeModerationResult(
|
|
141
|
+
expected?: ModerationTestSuiteResultFlag[],
|
|
142
|
+
context?: string,
|
|
143
|
+
stringifiedResult?: string,
|
|
144
|
+
ignoreCause?: boolean,
|
|
145
|
+
): void;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface AsymmetricMatchers {
|
|
149
|
+
toBeModerationResult(
|
|
150
|
+
expected?: ModerationTestSuiteResultFlag[],
|
|
151
|
+
context?: string,
|
|
152
|
+
stringifiedResult?: string,
|
|
153
|
+
ignoreCause?: boolean,
|
|
154
|
+
): any;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export class ModerationBehaviorSuiteRunner {
|
|
159
|
+
users: SuiteUsers;
|
|
160
|
+
configurations: SuiteConfigurations;
|
|
161
|
+
scenarios: SuiteScenarios;
|
|
162
|
+
|
|
163
|
+
constructor(users: SuiteUsers, configurations: SuiteConfigurations, scenarios: SuiteScenarios) {
|
|
164
|
+
this.users = users;
|
|
165
|
+
this.configurations = configurations;
|
|
166
|
+
this.scenarios = scenarios;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
postScenario(scenario: ModerationTestSuiteScenario) {
|
|
170
|
+
if (scenario.subject !== 'post') {
|
|
171
|
+
throw new Error('Scenario subject must be "post"');
|
|
172
|
+
}
|
|
173
|
+
const author = this.profileView(scenario.author, scenario.labels);
|
|
174
|
+
return m.postView({
|
|
175
|
+
record: m.post({
|
|
176
|
+
text: 'Post text',
|
|
177
|
+
}),
|
|
178
|
+
author,
|
|
179
|
+
labels: (scenario.labels.post || []).map((val) =>
|
|
180
|
+
m.label({ val, uri: `at://${author.did}/app.bsky.feed.post/fake` }),
|
|
181
|
+
),
|
|
182
|
+
embed: scenario.quoteAuthor
|
|
183
|
+
? m.embedRecordView({
|
|
184
|
+
record: m.post({
|
|
185
|
+
text: 'Quoted post text',
|
|
186
|
+
}),
|
|
187
|
+
labels: (scenario.labels.quotedPost || []).map((val) =>
|
|
188
|
+
m.label({
|
|
189
|
+
val,
|
|
190
|
+
uri: `at://${author.did}/app.bsky.feed.post/fake`,
|
|
191
|
+
}),
|
|
192
|
+
),
|
|
193
|
+
author: this.profileView(scenario.quoteAuthor, {
|
|
194
|
+
account: scenario.labels.quotedAccount,
|
|
195
|
+
}),
|
|
196
|
+
})
|
|
197
|
+
: undefined,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
profileScenario(scenario: ModerationTestSuiteScenario) {
|
|
202
|
+
if (scenario.subject !== 'profile') {
|
|
203
|
+
throw new Error('Scenario subject must be "profile"');
|
|
204
|
+
}
|
|
205
|
+
return this.profileView(scenario.author, scenario.labels);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
profileView(name: string, scenarioLabels: ModerationTestSuiteScenario['labels']) {
|
|
209
|
+
const def = this.users[name];
|
|
210
|
+
|
|
211
|
+
const labels: ComAtprotoLabelDefs.Label[] = [];
|
|
212
|
+
if (scenarioLabels.account) {
|
|
213
|
+
for (const l of scenarioLabels.account) {
|
|
214
|
+
labels.push(m.label({ val: l, uri: `did:web:${name}` }));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (scenarioLabels.profile) {
|
|
218
|
+
for (const l of scenarioLabels.profile) {
|
|
219
|
+
labels.push(
|
|
220
|
+
m.label({
|
|
221
|
+
val: l,
|
|
222
|
+
uri: `at://did:web:${name}/app.bsky.actor.profile/self`,
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return m.profileView({
|
|
229
|
+
handle: `${name}.test`,
|
|
230
|
+
labels,
|
|
231
|
+
viewer: m.actorViewerState({
|
|
232
|
+
muted: def.muted || def.mutedByList,
|
|
233
|
+
mutedByList: def.mutedByList ? m.listViewBasic({ name: 'Fake List' }) : undefined,
|
|
234
|
+
blockedBy: def.blockedBy,
|
|
235
|
+
blocking:
|
|
236
|
+
def.blocking || def.blockingByList ? 'at://did:web:self.test/app.bsky.graph.block/fake' : undefined,
|
|
237
|
+
blockingByList: def.blockingByList ? m.listViewBasic({ name: 'Fake List' }) : undefined,
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
moderationOptions(scenario: ModerationTestSuiteScenario): ModerationOptions {
|
|
243
|
+
return {
|
|
244
|
+
viewerDid: this.configurations[scenario.cfg].authed === false ? undefined : 'did:web:self.test',
|
|
245
|
+
prefs: {
|
|
246
|
+
adultContentEnabled: Boolean(this.configurations[scenario.cfg]?.adultContentEnabled),
|
|
247
|
+
globalLabelPrefs: this.configurations[scenario.cfg].settings || {},
|
|
248
|
+
prefsByLabelers: {
|
|
249
|
+
'did:plc:fake-labeler': {
|
|
250
|
+
labelPrefs: {},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
hiddenPosts: [],
|
|
254
|
+
keywordFilters: [],
|
|
255
|
+
temporaryMutes: [],
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
package/lib/behaviors.ts
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const LabelTarget = {
|
|
2
2
|
/** label is intended for account's content */
|
|
3
|
-
Content
|
|
3
|
+
Content: 'content',
|
|
4
4
|
/** label is intended for account's profile */
|
|
5
|
-
Profile
|
|
5
|
+
Profile: 'profile',
|
|
6
6
|
/** label is intended for account's content and profile */
|
|
7
|
-
Account
|
|
8
|
-
}
|
|
7
|
+
Account: 'account',
|
|
8
|
+
} as const;
|
|
9
|
+
export type LabelTarget = (typeof LabelTarget)[keyof typeof LabelTarget];
|
|
9
10
|
|
|
10
|
-
export
|
|
11
|
+
export const DisplayContext = {
|
|
11
12
|
/** content in expanded view */
|
|
12
|
-
ContentView
|
|
13
|
+
ContentView: 'contentView',
|
|
13
14
|
/** images or video contained in content */
|
|
14
|
-
ContentMedia
|
|
15
|
+
ContentMedia: 'contentMedia',
|
|
15
16
|
/** content in a list/feed */
|
|
16
|
-
ContentList
|
|
17
|
+
ContentList: 'contentList',
|
|
17
18
|
|
|
18
19
|
/** profile in expanded view */
|
|
19
|
-
ProfileView
|
|
20
|
+
ProfileView: 'profileView',
|
|
20
21
|
/** profile's avatar or banner */
|
|
21
|
-
ProfileMedia
|
|
22
|
+
ProfileMedia: 'profileMedia',
|
|
22
23
|
/** profile in a list */
|
|
23
|
-
ProfileList
|
|
24
|
-
}
|
|
24
|
+
ProfileList: 'profileList',
|
|
25
|
+
} as const;
|
|
26
|
+
export type DisplayContext = (typeof DisplayContext)[keyof typeof DisplayContext];
|
|
25
27
|
|
|
26
|
-
export
|
|
28
|
+
export const ModerationAction = {
|
|
27
29
|
/** should cause blurring */
|
|
28
|
-
Blur
|
|
30
|
+
Blur: 'blur',
|
|
29
31
|
/** should cause an alert */
|
|
30
|
-
Alert
|
|
32
|
+
Alert: 'alert',
|
|
31
33
|
/** should cause a notice */
|
|
32
|
-
Inform
|
|
33
|
-
}
|
|
34
|
+
Inform: 'inform',
|
|
35
|
+
} as const;
|
|
36
|
+
export type ModerationAction = (typeof ModerationAction)[keyof typeof ModerationAction];
|
|
34
37
|
|
|
35
38
|
export type BehaviorMapping = { [C in DisplayContext]?: ModerationAction };
|
|
36
39
|
export type LabelBehaviorMatrix = { [T in LabelTarget]: BehaviorMapping };
|