import { TestNetworkNoAppView } from '@atproto/dev-env' import { DEFAULT_LABEL_SETTINGS } from '../src' import './util/moderation-behavior' describe('agent', () => { let network: TestNetworkNoAppView beforeAll(async () => { network = await TestNetworkNoAppView.create({ dbPostgresSchema: 'api_moderation_prefs', }) }) afterAll(async () => { await network.close() }) it('migrates legacy content-label prefs (no mutations)', async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user1.test', email: 'user1@test.com', password: 'password', }) await agent.app.bsky.actor.putPreferences({ preferences: [ { $type: 'app.bsky.actor.defs#contentLabelPref', label: 'porn', visibility: 'show', }, { $type: 'app.bsky.actor.defs#contentLabelPref', label: 'nudity', visibility: 'show', }, { $type: 'app.bsky.actor.defs#contentLabelPref', label: 'sexual', visibility: 'show', }, { $type: 'app.bsky.actor.defs#contentLabelPref', label: 'graphic-media', visibility: 'show', }, ], }) await expect(agent.getPreferences()).resolves.toStrictEqual({ feeds: { pinned: undefined, saved: undefined, }, savedFeeds: expect.any(Array), interests: { tags: [] }, moderationPrefs: { adultContentEnabled: false, labels: { porn: 'ignore', nudity: 'ignore', sexual: 'ignore', 'graphic-media': 'ignore', }, labelers: [...agent.appLabelers.map((did) => ({ did, labels: {} }))], hiddenPosts: [], mutedWords: [], }, birthDate: undefined, feedViewPrefs: { home: { hideQuotePosts: false, hideReplies: false, hideRepliesByLikeCount: 0, hideRepliesByUnfollowed: true, hideReposts: false, }, }, threadViewPrefs: { prioritizeFollowedUsers: true, sort: 'hotness', }, bskyAppState: { activeProgressGuide: undefined, queuedNudges: [], nuxs: [], }, }) }) it('adds/removes moderation services', async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user5.test', email: 'user5@test.com', password: 'password', }) await agent.addLabeler('did:plc:other') expect(agent.labelers).toStrictEqual(['did:plc:other']) await expect(agent.getPreferences()).resolves.toStrictEqual({ feeds: { pinned: undefined, saved: undefined }, savedFeeds: expect.any(Array), interests: { tags: [] }, moderationPrefs: { adultContentEnabled: false, labels: DEFAULT_LABEL_SETTINGS, labelers: [ ...agent.appLabelers.map((did) => ({ did, labels: {} })), { did: 'did:plc:other', labels: {}, }, ], hiddenPosts: [], mutedWords: [], }, birthDate: undefined, feedViewPrefs: { home: { hideReplies: false, hideRepliesByUnfollowed: true, hideRepliesByLikeCount: 0, hideReposts: false, hideQuotePosts: false, }, }, threadViewPrefs: { sort: 'hotness', prioritizeFollowedUsers: true, }, bskyAppState: { activeProgressGuide: undefined, queuedNudges: [], nuxs: [], }, }) expect(agent.labelers).toStrictEqual(['did:plc:other']) await agent.removeLabeler('did:plc:other') expect(agent.labelers).toStrictEqual([]) await expect(agent.getPreferences()).resolves.toStrictEqual({ feeds: { pinned: undefined, saved: undefined }, savedFeeds: expect.any(Array), interests: { tags: [] }, moderationPrefs: { adultContentEnabled: false, labels: DEFAULT_LABEL_SETTINGS, labelers: [...agent.appLabelers.map((did) => ({ did, labels: {} }))], hiddenPosts: [], mutedWords: [], }, birthDate: undefined, feedViewPrefs: { home: { hideReplies: false, hideRepliesByUnfollowed: true, hideRepliesByLikeCount: 0, hideReposts: false, hideQuotePosts: false, }, }, threadViewPrefs: { sort: 'hotness', prioritizeFollowedUsers: true, }, bskyAppState: { activeProgressGuide: undefined, queuedNudges: [], nuxs: [], }, }) expect(agent.labelers).toStrictEqual([]) }) it('sets label preferences globally and per-moderator', async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user7.test', email: 'user7@test.com', password: 'password', }) await agent.addLabeler('did:plc:other') await agent.setContentLabelPref('porn', 'ignore') await agent.setContentLabelPref('porn', 'hide', 'did:plc:other') await agent.setContentLabelPref('x-custom', 'warn', 'did:plc:other') await expect(agent.getPreferences()).resolves.toStrictEqual({ feeds: { pinned: undefined, saved: undefined }, savedFeeds: expect.any(Array), interests: { tags: [] }, moderationPrefs: { adultContentEnabled: false, labels: { ...DEFAULT_LABEL_SETTINGS, porn: 'ignore', nsfw: 'ignore' }, labelers: [ ...agent.appLabelers.map((did) => ({ did, labels: {} })), { did: 'did:plc:other', labels: { porn: 'hide', 'x-custom': 'warn', }, }, ], hiddenPosts: [], mutedWords: [], }, birthDate: undefined, feedViewPrefs: { home: { hideReplies: false, hideRepliesByUnfollowed: true, hideRepliesByLikeCount: 0, hideReposts: false, hideQuotePosts: false, }, }, threadViewPrefs: { sort: 'hotness', prioritizeFollowedUsers: true, }, bskyAppState: { activeProgressGuide: undefined, queuedNudges: [], nuxs: [], }, }) }) it(`updates label pref`, async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user8.test', email: 'user8@test.com', password: 'password', }) await agent.addLabeler('did:plc:other') await agent.setContentLabelPref('porn', 'ignore') await agent.setContentLabelPref('porn', 'ignore', 'did:plc:other') await agent.setContentLabelPref('porn', 'hide') await agent.setContentLabelPref('porn', 'hide', 'did:plc:other') const { moderationPrefs } = await agent.getPreferences() const labeler = moderationPrefs.labelers.find( (l) => l.did === 'did:plc:other', ) expect(moderationPrefs.labels.porn).toEqual('hide') expect(labeler?.labels?.porn).toEqual('hide') }) it(`double-write for legacy: 'graphic-media' in sync with 'gore'`, async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user9.test', email: 'user9@test.com', password: 'password', }) await agent.setContentLabelPref('graphic-media', 'hide') const a = await agent.getPreferences() expect(a.moderationPrefs.labels.gore).toEqual('hide') expect(a.moderationPrefs.labels['graphic-media']).toEqual('hide') await agent.setContentLabelPref('graphic-media', 'warn') const b = await agent.getPreferences() expect(b.moderationPrefs.labels.gore).toEqual('warn') expect(b.moderationPrefs.labels['graphic-media']).toEqual('warn') }) it(`double-write for legacy: 'porn' in sync with 'nsfw'`, async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user10.test', email: 'user10@test.com', password: 'password', }) await agent.setContentLabelPref('porn', 'hide') const a = await agent.getPreferences() expect(a.moderationPrefs.labels.nsfw).toEqual('hide') expect(a.moderationPrefs.labels.porn).toEqual('hide') await agent.setContentLabelPref('porn', 'warn') const b = await agent.getPreferences() expect(b.moderationPrefs.labels.nsfw).toEqual('warn') expect(b.moderationPrefs.labels.porn).toEqual('warn') }) it(`double-write for legacy: 'sexual' in sync with 'suggestive'`, async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user11.test', email: 'user11@test.com', password: 'password', }) await agent.setContentLabelPref('sexual', 'hide') const a = await agent.getPreferences() expect(a.moderationPrefs.labels.sexual).toEqual('hide') expect(a.moderationPrefs.labels.suggestive).toEqual('hide') await agent.setContentLabelPref('sexual', 'warn') const b = await agent.getPreferences() expect(b.moderationPrefs.labels.sexual).toEqual('warn') expect(b.moderationPrefs.labels.suggestive).toEqual('warn') }) it(`double-write for legacy: filters out existing old label pref if double-written`, async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user12.test', email: 'user12@test.com', password: 'password', }) await agent.setContentLabelPref('nsfw', 'hide') await agent.setContentLabelPref('porn', 'hide') const a = await agent.app.bsky.actor.getPreferences({}) const nsfwSettings = a.data.preferences.filter( (pref) => pref.label === 'nsfw', ) expect(nsfwSettings.length).toEqual(1) }) it(`remaps old values to new on read`, async () => { const agent = network.pds.getClient() await agent.createAccount({ handle: 'user13.test', email: 'user13@test.com', password: 'password', }) await agent.setContentLabelPref('nsfw', 'hide') await agent.setContentLabelPref('gore', 'hide') await agent.setContentLabelPref('suggestive', 'hide') const a = await agent.getPreferences() expect(a.moderationPrefs.labels.porn).toEqual('hide') expect(a.moderationPrefs.labels['graphic-media']).toEqual('hide') expect(a.moderationPrefs.labels['sexual']).toEqual('hide') }) })