atproto/packages/pds/tests/blob-deletes.test.ts
Daniel Holmgren de2dbc2903
Split out moderation backend ()
* mv appview

* copy

* finalize copy

* package names

* big WIP

* first pass at mod servce

* some tidy

* tidy & fix compiler errors

* rename to ozone, db migrations, add to dev-env & pds cfg

* getRecord & getRepo mostly working

* fix open handle

* get record tests all working

* moderation events working

* statuses working

* tidy test suite

* search repos

* server & db tests

* moderation tests

* wip daemon + push events

* pds fanout working

* fix db test

* fanning takedowns out to appview

* rm try/catch

* bsky moderation test

* introduce mod subject wrappers

* more tidy

* refactor event reversal

* tidy some db stuff

* tidy

* rename service to mod-service

* fix test

* tidy config

* refactor auth in bsky

* wip patching up auto-mod

* add label ingester in appview

* fix a couple build issues

* fix some timing bugs

* tidy polling logic

* fix up tests

* fix some pds tests

* eslint ignore

* fix ozone tests

* move seeds to dev-env

* move images around

* fix db schemas

* use service auth admin reqs

* fix remaining tests

* auth tests bsky

* another test

* random tidy

* fix up search

* clean up bsky mod service

* more tidy

* default attempts to 0

* tidy old test

* random tidy

* tidy package.json

* tidy logger

* takedownId -> takedownRef

* misc pr feedback

* split daemon out from ozone application

* fix blob takedown mgiration

* refactor ozone config

* do push event fanout on write instead of on read

* make suspend error work again

* add attempts check & add supporting index

* fix takedown test ref

* get tests working

* rm old test

* fix timing bug in event pusher tests

* attempt another fix for timing bug

* await req

* service files

* remove labelerDid cfg

* update snaps for labeler did + some cfg changes

* fix more snaps

* pnpm i

* build ozone images

* build

* make label provider optional

* fix build issues

* fix build

* fix build

* build pds

* build on ghcr

* fix syntax in entry

* another fix

* use correct import

* export logger

* remove event reverser

* adjust push event fanout

* push out multiple

* remove builds
2024-01-05 17:06:54 -06:00

206 lines
5.7 KiB
TypeScript

import { TestNetworkNoAppView, SeedClient } from '@atproto/dev-env'
import AtpAgent, { BlobRef } from '@atproto/api'
import { ids } from '../src/lexicon/lexicons'
import { AppContext } from '../src'
describe('blob deletes', () => {
let network: TestNetworkNoAppView
let agent: AtpAgent
let sc: SeedClient
let ctx: AppContext
let alice: string
let bob: string
beforeAll(async () => {
network = await TestNetworkNoAppView.create({
dbPostgresSchema: 'blob_deletes',
})
ctx = network.pds.ctx
agent = network.pds.getClient()
sc = network.getSeedClient()
await sc.createAccount('alice', {
email: 'alice@test.com',
handle: 'alice.test',
password: 'alice-pass',
})
await sc.createAccount('bob', {
email: 'bob@test.com',
handle: 'bob.test',
password: 'bob-pass',
})
alice = sc.dids.alice
bob = sc.dids.bob
})
afterAll(async () => {
await network.close()
})
const getDbBlobsForDid = (did: string) => {
return ctx.actorStore.read(did, (store) =>
store.db.db.selectFrom('blob').selectAll().execute(),
)
}
it('deletes blob when record is deleted', async () => {
const img = await sc.uploadFile(
alice,
'../dev-env/src/seed/img/key-portrait-small.jpg',
'image/jpeg',
)
const post = await sc.post(alice, 'test', undefined, [img])
await sc.deletePost(alice, post.ref.uri)
await network.processAll()
const dbBlobs = await getDbBlobsForDid(alice)
expect(dbBlobs.length).toBe(0)
const hasImg = await ctx.blobstore(alice).hasStored(img.image.ref)
expect(hasImg).toBeFalsy()
})
it('deletes blob when blob-ref in record is updated', async () => {
const img = await sc.uploadFile(
alice,
'../dev-env/src/seed/img/key-portrait-small.jpg',
'image/jpeg',
)
const img2 = await sc.uploadFile(
alice,
'../dev-env/src/seed/img/key-landscape-small.jpg',
'image/jpeg',
)
await updateProfile(sc, alice, img.image, img.image)
await updateProfile(sc, alice, img2.image, img2.image)
await network.processAll()
const dbBlobs = await getDbBlobsForDid(alice)
expect(dbBlobs.length).toBe(1)
expect(dbBlobs[0].cid).toEqual(img2.image.ref.toString())
const hasImg = await ctx.blobstore(alice).hasStored(img.image.ref)
expect(hasImg).toBeFalsy()
const hasImg2 = await ctx.blobstore(alice).hasStored(img2.image.ref)
expect(hasImg2).toBeTruthy()
// reset
await updateProfile(sc, alice)
})
it('does not delete blob when blob-ref in record is not updated', async () => {
const img = await sc.uploadFile(
alice,
'../dev-env/src/seed/img/key-portrait-small.jpg',
'image/jpeg',
)
const img2 = await sc.uploadFile(
alice,
'../dev-env/src/seed/img/key-landscape-small.jpg',
'image/jpeg',
)
await updateProfile(sc, alice, img.image, img.image)
await updateProfile(sc, alice, img.image, img2.image)
await network.processAll()
const dbBlobs = await getDbBlobsForDid(alice)
expect(dbBlobs.length).toBe(2)
const hasImg = await ctx.blobstore(alice).hasStored(img.image.ref)
expect(hasImg).toBeTruthy()
const hasImg2 = await ctx.blobstore(alice).hasStored(img2.image.ref)
expect(hasImg2).toBeTruthy()
await updateProfile(sc, alice)
})
it('does not delete blob when blob is reused by another record in same commit', async () => {
const img = await sc.uploadFile(
alice,
'../dev-env/src/seed/img/key-portrait-small.jpg',
'image/jpeg',
)
const post = await sc.post(alice, 'post', undefined, [img])
await agent.com.atproto.repo.applyWrites(
{
repo: alice,
writes: [
{
$type: 'com.atproto.repo.applyWrites#delete',
collection: 'app.bsky.feed.post',
rkey: post.ref.uri.rkey,
},
{
$type: 'com.atproto.repo.applyWrites#create',
collection: 'app.bsky.feed.post',
value: {
text: 'post2',
embed: {
$type: 'app.bsky.embed.images',
images: [
{
image: img.image,
alt: 'alt',
},
],
},
createdAt: new Date().toISOString(),
},
},
],
},
{ encoding: 'application/json', headers: sc.getHeaders(alice) },
)
await network.processAll()
const dbBlobs = await getDbBlobsForDid(alice)
expect(dbBlobs.length).toBe(1)
const hasImg = await ctx.blobstore(alice).hasStored(img.image.ref)
expect(hasImg).toBeTruthy()
})
it('does delete blob from user blob store if another user is using it', async () => {
const imgAlice = await sc.uploadFile(
alice,
'../dev-env/src/seed/img/key-landscape-small.jpg',
'image/jpeg',
)
const imgBob = await sc.uploadFile(
bob,
'../dev-env/src/seed/img/key-landscape-small.jpg',
'image/jpeg',
)
const postAlice = await sc.post(alice, 'post', undefined, [imgAlice])
await sc.post(bob, 'post', undefined, [imgBob])
await sc.deletePost(alice, postAlice.ref.uri)
await network.processAll()
const hasImg = await ctx.blobstore(alice).hasStored(imgAlice.image.ref)
expect(hasImg).toBeFalsy()
})
})
async function updateProfile(
sc: SeedClient,
did: string,
avatar?: BlobRef,
banner?: BlobRef,
) {
return await sc.agent.api.com.atproto.repo.putRecord(
{
repo: did,
collection: ids.AppBskyActorProfile,
rkey: 'self',
record: {
avatar: avatar,
banner: banner,
},
},
{ encoding: 'application/json', headers: sc.getHeaders(did) },
)
}