import { AtpAgent } from '@atproto/api' import { TestNetwork, SeedClient, RecordRef, basicSeed } from '@atproto/dev-env' import { forSnapshot, paginateAll, stripViewer, stripViewerFromPost, } from '../_util' import { ids } from '../../src/lexicon/lexicons' describe('list feed views', () => { let network: TestNetwork let agent: AtpAgent let sc: SeedClient // account dids, for convenience let alice: string let bob: string let carol: string let listRef: RecordRef beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_views_list_feed', }) agent = network.bsky.getClient() sc = network.getSeedClient() await basicSeed(sc) alice = sc.dids.alice bob = sc.dids.bob carol = sc.dids.carol listRef = await sc.createList(alice, 'test list', 'curate') await sc.addToList(alice, alice, listRef) await sc.addToList(alice, bob, listRef) await network.processAll() }) afterAll(async () => { await network.close() }) it('fetches list feed', async () => { const res = await agent.api.app.bsky.feed.getListFeed( { list: listRef.uriStr }, { headers: await network.serviceHeaders( carol, ids.AppBskyFeedGetListFeed, ), }, ) expect(forSnapshot(res.data.feed)).toMatchSnapshot() // all posts are from alice or bob expect( res.data.feed.every((row) => [alice, bob].includes(row.post.author.did)), ).toBeTruthy() }) it('paginates', async () => { const results = (results) => results.flatMap((res) => res.feed) const paginator = async (cursor?: string) => { const res = await agent.api.app.bsky.feed.getListFeed( { list: listRef.uriStr, cursor, limit: 2, }, { headers: await network.serviceHeaders( carol, ids.AppBskyFeedGetListFeed, ), }, ) return res.data } const paginatedAll = await paginateAll(paginator) paginatedAll.forEach((res) => expect(res.feed.length).toBeLessThanOrEqual(2), ) const full = await agent.api.app.bsky.feed.getListFeed( { list: listRef.uriStr }, { headers: await network.serviceHeaders( carol, ids.AppBskyFeedGetListFeed, ), }, ) expect(full.data.feed.length).toEqual(7) expect(results(paginatedAll)).toEqual(results([full.data])) }) it('fetches results unauthed', async () => { const { data: authed } = await agent.api.app.bsky.feed.getListFeed( { list: listRef.uriStr }, { headers: await network.serviceHeaders( alice, ids.AppBskyFeedGetListFeed, ), }, ) const { data: unauthed } = await agent.api.app.bsky.feed.getListFeed({ list: listRef.uriStr, }) expect(unauthed.feed.length).toBeGreaterThan(0) expect(unauthed.feed).toEqual( authed.feed.map((item) => { const result = { ...item, post: stripViewerFromPost(item.post), } if (item.reply) { result.reply = { parent: stripViewerFromPost(item.reply.parent), root: stripViewerFromPost(item.reply.root), grandparentAuthor: item.reply.grandparentAuthor && stripViewer(item.reply.grandparentAuthor), } } return result }), ) }) it('works for empty lists', async () => { const emptyList = await sc.createList(alice, 'empty list', 'curate') const res = await agent.api.app.bsky.feed.getListFeed({ list: emptyList.uriStr, }) expect(res.data.feed.length).toEqual(0) }) it('blocks posts by actor takedown', async () => { await network.bsky.ctx.dataplane.takedownActor({ did: bob, }) const res = await agent.api.app.bsky.feed.getListFeed({ list: listRef.uriStr, }) const hasBob = res.data.feed.some((item) => item.post.author.did === bob) expect(hasBob).toBe(false) // Cleanup await network.bsky.ctx.dataplane.untakedownActor({ did: bob, }) }) it('blocks posts by record takedown.', async () => { const postRef = sc.replies[bob][0].ref // Post and reply parent await network.bsky.ctx.dataplane.takedownRecord({ recordUri: postRef.uriStr, }) const res = await agent.api.app.bsky.feed.getListFeed({ list: listRef.uriStr, }) const hasPost = res.data.feed.some( (item) => item.post.uri === postRef.uriStr, ) expect(hasPost).toBe(false) // Cleanup await network.bsky.ctx.dataplane.untakedownRecord({ recordUri: postRef.uriStr, }) }) it('does not return posts with creator blocks', async () => { await sc.block(bob, alice) await network.processAll() const res = await agent.api.app.bsky.feed.getListFeed({ list: listRef.uriStr, }) const hasBob = res.data.feed.some((item) => item.post.author.did === bob) expect(hasBob).toBe(false) }) })