atproto/packages/pds/tests/blob-deletes.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

207 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',
})
// @ts-expect-error Error due to circular dependency with the dev-env package
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/assets/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/assets/key-portrait-small.jpg',
'image/jpeg',
)
const img2 = await sc.uploadFile(
alice,
'../dev-env/assets/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/assets/key-portrait-small.jpg',
'image/jpeg',
)
const img2 = await sc.uploadFile(
alice,
'../dev-env/assets/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/assets/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/assets/key-landscape-small.jpg',
'image/jpeg',
)
const imgBob = await sc.uploadFile(
bob,
'../dev-env/assets/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) },
)
}