638f5a8312
* Fix avatar path resolution in dev-env * changeset * extract dev-env assets to dedicated folder * add comment * fix fmt
207 lines
5.7 KiB
TypeScript
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) },
|
|
)
|
|
}
|