atproto/packages/pds/tests/moderation.test.ts
rafael 638f5a8312
Fix avatar path resolution in dev-env (#3266)
* Fix avatar path resolution in dev-env

* changeset

* extract dev-env assets to dedicated folder

* add comment

* fix fmt
2024-12-19 11:25:41 -03:00

274 lines
8.2 KiB
TypeScript

import { AtpAgent } from '@atproto/api'
import { TestNetworkNoAppView, ImageRef, SeedClient } from '@atproto/dev-env'
import { BlobNotFoundError } from '@atproto/repo'
import basicSeed from './seeds/basic'
import {
RepoBlobRef,
RepoRef,
} from '../src/lexicon/types/com/atproto/admin/defs'
import { Main as StrongRef } from '../src/lexicon/types/com/atproto/repo/strongRef'
describe('moderation', () => {
let network: TestNetworkNoAppView
let agent: AtpAgent
let sc: SeedClient
let repoSubject: RepoRef
let recordSubject: StrongRef
let blobSubject: RepoBlobRef
let blobRef: ImageRef
beforeAll(async () => {
network = await TestNetworkNoAppView.create({
dbPostgresSchema: 'moderation',
})
agent = network.pds.getClient()
sc = network.getSeedClient()
await basicSeed(sc)
await network.processAll()
repoSubject = {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.bob,
}
const post = sc.posts[sc.dids.carol][0]
recordSubject = {
$type: 'com.atproto.repo.strongRef',
uri: post.ref.uriStr,
cid: post.ref.cidStr,
}
blobRef = post.images[1]
blobSubject = {
$type: 'com.atproto.admin.defs#repoBlobRef',
did: sc.dids.carol,
cid: blobRef.image.ref.toString(),
}
})
afterAll(async () => {
await network.close()
})
it('takes down accounts', async () => {
await agent.api.com.atproto.admin.updateSubjectStatus(
{
subject: repoSubject,
takedown: { applied: true, ref: 'test-repo' },
},
{
encoding: 'application/json',
headers: network.pds.adminAuthHeaders(),
},
)
const res = await agent.api.com.atproto.admin.getSubjectStatus(
{
did: repoSubject.did,
},
{ headers: network.pds.adminAuthHeaders() },
)
expect(res.data.subject.did).toEqual(sc.dids.bob)
expect(res.data.takedown?.applied).toBe(true)
expect(res.data.takedown?.ref).toBe('test-repo')
})
it('restores takendown accounts', async () => {
await agent.api.com.atproto.admin.updateSubjectStatus(
{
subject: repoSubject,
takedown: { applied: false },
},
{
encoding: 'application/json',
headers: network.pds.adminAuthHeaders(),
},
)
const res = await agent.api.com.atproto.admin.getSubjectStatus(
{
did: repoSubject.did,
},
{ headers: network.pds.adminAuthHeaders() },
)
expect(res.data.subject.did).toEqual(sc.dids.bob)
expect(res.data.takedown?.applied).toBe(false)
expect(res.data.takedown?.ref).toBeUndefined()
})
it('takes down records', async () => {
await agent.api.com.atproto.admin.updateSubjectStatus(
{
subject: recordSubject,
takedown: { applied: true, ref: 'test-record' },
},
{
encoding: 'application/json',
headers: network.pds.adminAuthHeaders(),
},
)
const res = await agent.api.com.atproto.admin.getSubjectStatus(
{
uri: recordSubject.uri,
},
{ headers: network.pds.adminAuthHeaders() },
)
expect(res.data.subject.uri).toEqual(recordSubject.uri)
expect(res.data.takedown?.applied).toBe(true)
expect(res.data.takedown?.ref).toBe('test-record')
})
it('restores takendown records', async () => {
await agent.api.com.atproto.admin.updateSubjectStatus(
{
subject: recordSubject,
takedown: { applied: false },
},
{
encoding: 'application/json',
headers: network.pds.adminAuthHeaders(),
},
)
const res = await agent.api.com.atproto.admin.getSubjectStatus(
{
uri: recordSubject.uri,
},
{ headers: network.pds.adminAuthHeaders() },
)
expect(res.data.subject.uri).toEqual(recordSubject.uri)
expect(res.data.takedown?.applied).toBe(false)
expect(res.data.takedown?.ref).toBeUndefined()
})
describe('blob takedown', () => {
it('takes down blobs', async () => {
await agent.api.com.atproto.admin.updateSubjectStatus(
{
subject: blobSubject,
takedown: { applied: true, ref: 'test-blob' },
},
{
encoding: 'application/json',
headers: network.pds.adminAuthHeaders(),
},
)
const res = await agent.api.com.atproto.admin.getSubjectStatus(
{
did: blobSubject.did,
blob: blobSubject.cid,
},
{ headers: network.pds.adminAuthHeaders() },
)
expect(res.data.subject.did).toEqual(blobSubject.did)
expect(res.data.subject.cid).toEqual(blobSubject.cid)
expect(res.data.takedown?.applied).toBe(true)
expect(res.data.takedown?.ref).toBe('test-blob')
})
it('removes blob from the store', async () => {
const tryGetBytes = network.pds.ctx
.blobstore(blobSubject.did)
.getBytes(blobRef.image.ref)
await expect(tryGetBytes).rejects.toThrow(BlobNotFoundError)
})
it('prevents blob from being referenced again.', async () => {
const referenceBlob = sc.post(sc.dids.carol, 'pic', [], [blobRef])
await expect(referenceBlob).rejects.toThrow('Could not find blob:')
})
it('prevents blob from being reuploaded', async () => {
const attempt = sc.uploadFile(
sc.dids.carol,
'../dev-env/assets/key-alt.jpg',
'image/jpeg',
)
await expect(attempt).rejects.toThrow(
'Blob has been takendown, cannot re-upload',
)
})
it('prevents image blob from being served.', async () => {
const attempt = agent.api.com.atproto.sync.getBlob({
did: sc.dids.carol,
cid: blobRef.image.ref.toString(),
})
await expect(attempt).rejects.toThrow('Blob not found')
})
it('restores blob when takedown is removed', async () => {
await agent.api.com.atproto.admin.updateSubjectStatus(
{
subject: blobSubject,
takedown: { applied: false },
},
{
encoding: 'application/json',
headers: network.pds.adminAuthHeaders(),
},
)
// Can post and reference blob
const post = await sc.post(sc.dids.carol, 'pic', [], [blobRef])
expect(post.images[0].image.ref.equals(blobRef.image.ref)).toBeTruthy()
// Can fetch through image server
const res = await agent.api.com.atproto.sync.getBlob({
did: sc.dids.carol,
cid: blobRef.image.ref.toString(),
})
expect(res.data.byteLength).toBeGreaterThan(9000)
})
it('prevents blobs of takendown accounts from being served.', async () => {
await agent.api.com.atproto.admin.updateSubjectStatus(
{
subject: {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.carol,
},
takedown: { applied: true },
},
{
encoding: 'application/json',
headers: network.pds.adminAuthHeaders(),
},
)
const blobParams = {
did: sc.dids.carol,
cid: blobRef.image.ref.toString(),
}
// public, disallow
const attempt1 = agent.api.com.atproto.sync.getBlob(blobParams)
await expect(attempt1).rejects.toThrow(/Repo has been takendown/)
// logged-in, disallow
const attempt2 = agent.api.com.atproto.sync.getBlob(blobParams, {
headers: sc.getHeaders(sc.dids.bob),
})
await expect(attempt2).rejects.toThrow(/Repo has been takendown/)
// logged-in as account, allow
const res1 = await agent.api.com.atproto.sync.getBlob(blobParams, {
headers: sc.getHeaders(sc.dids.carol),
})
expect(res1.data.byteLength).toBeGreaterThan(9000)
// admin role, allow
const res2 = await agent.api.com.atproto.sync.getBlob(blobParams, {
headers: network.pds.adminAuthHeaders(),
})
expect(res2.data.byteLength).toBeGreaterThan(9000)
// revert takedown
await agent.api.com.atproto.admin.updateSubjectStatus(
{
subject: {
$type: 'com.atproto.admin.defs#repoRef',
did: sc.dids.carol,
},
takedown: { applied: false },
},
{
encoding: 'application/json',
headers: network.pds.adminAuthHeaders(),
},
)
})
})
})