Skip to content

Commit

Permalink
Return empty cursor if feed echoes input cursor (bluesky-social#2989)
Browse files Browse the repository at this point in the history
* Throw if custom feed echoes input cursor out

* Return empty cursor instead of throwing
  • Loading branch information
rafaelbsky authored Nov 18, 2024
1 parent 90399c8 commit 2d9a2a8
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 36 deletions.
9 changes: 8 additions & 1 deletion packages/bsky/src/api/app/bsky/feed/getFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ const skeletonFromFeedGen = async (
): Promise<AlgoResponse> => {
const { feed, headers } = params
const found = await ctx.hydrator.feed.getFeedGens([feed], true)
const feedDid = await found.get(feed)?.record.did
const feedDid = found.get(feed)?.record.did
if (!feedDid) {
throw new InvalidRequestError('could not find feed')
}
Expand Down Expand Up @@ -220,7 +220,14 @@ const skeletonFromFeedGen = async (
headers,
},
)

skeleton = result.data

if (result.data.cursor === params.cursor) {
// Prevents loops if the custom feed echoes the the input cursor back.
skeleton.cursor = undefined
}

if (result.headers['content-language']) {
resHeaders = {
'content-language': result.headers['content-language'],
Expand Down
56 changes: 49 additions & 7 deletions packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,9 @@ Array [
"muted": false,
},
},
"description": "Provides all feed candidates, blindly ignoring pagination limit",
"description": "Echoes back the same cursor it received",
"did": "user(0)",
"displayName": "Bad Pagination",
"displayName": "Bad Pagination Cursor",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
Expand Down Expand Up @@ -439,9 +439,9 @@ Array [
"muted": false,
},
},
"description": "Provides even-indexed feed candidates",
"description": "Provides all feed candidates, blindly ignoring pagination limit",
"did": "user(0)",
"displayName": "Even",
"displayName": "Bad Pagination Limit",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
Expand Down Expand Up @@ -481,15 +481,57 @@ Array [
"muted": false,
},
},
"description": "Provides even-indexed feed candidates",
"did": "user(0)",
"displayName": "Even",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
"uri": "record(7)",
"viewer": Object {},
},
Object {
"cid": "cids(7)",
"creator": Object {
"avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg",
"createdAt": "1970-01-01T00:00:00.000Z",
"description": "its me!",
"did": "user(1)",
"displayName": "ali",
"handle": "alice.test",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"src": "user(1)",
"uri": "record(3)",
"val": "self-label-a",
},
Object {
"cid": "cids(2)",
"cts": "1970-01-01T00:00:00.000Z",
"src": "user(1)",
"uri": "record(3)",
"val": "self-label-b",
},
],
"viewer": Object {
"blockedBy": false,
"followedBy": "record(2)",
"following": "record(1)",
"muted": false,
},
},
"description": "Provides all feed candidates",
"did": "user(0)",
"displayName": "All",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 2,
"uri": "record(7)",
"uri": "record(8)",
"viewer": Object {
"like": "record(8)",
"like": "record(9)",
},
},
]
Expand Down Expand Up @@ -1950,7 +1992,7 @@ Object {
},
"description": "Provides all feed candidates, blindly ignoring pagination limit",
"did": "user(0)",
"displayName": "Bad Pagination",
"displayName": "Bad Pagination Limit",
"indexedAt": "1970-01-01T00:00:00.000Z",
"labels": Array [],
"likeCount": 0,
Expand Down
110 changes: 82 additions & 28 deletions packages/bsky/tests/feed-generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ describe('feed generation', () => {
let feedUriAllRef: RecordRef
let feedUriEven: string
let feedUriOdd: string // Unsupported by feed gen
let feedUriBadPagination: string
let feedUriBadPaginationLimit: string
let feedUriBadPaginationCursor: string
let feedUriPrime: string // Taken-down
let feedUriPrimeRef: RecordRef
let feedUriNeedsAuth: string
Expand All @@ -47,10 +48,15 @@ describe('feed generation', () => {
await network.processAll()
alice = sc.dids.alice
const allUri = AtUri.make(alice, 'app.bsky.feed.generator', 'all')
const feedUriBadPagination = AtUri.make(
const feedUriBadPaginationLimit = AtUri.make(
alice,
'app.bsky.feed.generator',
'bad-pagination',
'bad-pagination-limit',
)
const feedUriBadPaginationCursor = AtUri.make(
alice,
'app.bsky.feed.generator',
'bad-pagination-cursor',
)
const evenUri = AtUri.make(alice, 'app.bsky.feed.generator', 'even')
const primeUri = AtUri.make(alice, 'app.bsky.feed.generator', 'prime')
Expand All @@ -62,15 +68,20 @@ describe('feed generation', () => {
gen = await network.createFeedGen({
[allUri.toString()]: feedGenHandler('all'),
[evenUri.toString()]: feedGenHandler('even'),
[feedUriBadPagination.toString()]: feedGenHandler('bad-pagination'),
[feedUriBadPaginationLimit.toString()]: feedGenHandler(
'bad-pagination-limit',
),
[feedUriBadPaginationCursor.toString()]: feedGenHandler(
'bad-pagination-cursor',
),
[primeUri.toString()]: feedGenHandler('prime'),
[needsAuthUri.toString()]: feedGenHandler('needs-auth'),
})

const feedSuggestions = [
{ uri: allUri.toString(), order: 1 },
{ uri: evenUri.toString(), order: 2 },
{ uri: feedUriBadPagination.toString(), order: 3 },
{ uri: feedUriBadPaginationLimit.toString(), order: 3 },
{ uri: primeUri.toString(), order: 4 },
]
await network.bsky.db.db
Expand Down Expand Up @@ -116,17 +127,31 @@ describe('feed generation', () => {
},
sc.getHeaders(alice),
)
const badPagination = await pdsAgent.api.app.bsky.feed.generator.create(
{ repo: alice, rkey: 'bad-pagination' },
{
did: gen.did,
displayName: 'Bad Pagination',
description:
'Provides all feed candidates, blindly ignoring pagination limit',
createdAt: new Date().toISOString(),
},
sc.getHeaders(alice),
)

const badPaginationLimit =
await pdsAgent.api.app.bsky.feed.generator.create(
{ repo: alice, rkey: 'bad-pagination-limit' },
{
did: gen.did,
displayName: 'Bad Pagination Limit',
description:
'Provides all feed candidates, blindly ignoring pagination limit',
createdAt: new Date().toISOString(),
},
sc.getHeaders(alice),
)
const badPaginationCursor =
await pdsAgent.api.app.bsky.feed.generator.create(
{ repo: alice, rkey: 'bad-pagination-cursor' },
{
did: gen.did,
displayName: 'Bad Pagination Cursor',
description: 'Echoes back the same cursor it received',
createdAt: new Date().toISOString(),
},
sc.getHeaders(alice),
)

// Taken-down
const prime = await pdsAgent.api.app.bsky.feed.generator.create(
{ repo: alice, rkey: 'prime' },
Expand Down Expand Up @@ -157,7 +182,8 @@ describe('feed generation', () => {
feedUriAllRef = new RecordRef(all.uri, all.cid)
feedUriEven = even.uri
feedUriOdd = odd.uri
feedUriBadPagination = badPagination.uri
feedUriBadPaginationLimit = badPaginationLimit.uri
feedUriBadPaginationCursor = badPaginationCursor.uri
feedUriPrime = prime.uri
feedUriPrimeRef = new RecordRef(prime.uri, prime.cid)
feedUriNeedsAuth = needsAuth.uri
Expand Down Expand Up @@ -203,12 +229,13 @@ describe('feed generation', () => {

const paginatedAll = results(await paginateAll(paginator))

expect(paginatedAll.length).toEqual(5)
expect(paginatedAll.length).toEqual(6)
expect(paginatedAll[0].uri).toEqual(feedUriOdd)
expect(paginatedAll[1].uri).toEqual(feedUriNeedsAuth)
expect(paginatedAll[2].uri).toEqual(feedUriBadPagination)
expect(paginatedAll[3].uri).toEqual(feedUriEven)
expect(paginatedAll[4].uri).toEqual(feedUriAll)
expect(paginatedAll[2].uri).toEqual(feedUriBadPaginationCursor)
expect(paginatedAll[3].uri).toEqual(feedUriBadPaginationLimit)
expect(paginatedAll[4].uri).toEqual(feedUriEven)
expect(paginatedAll[5].uri).toEqual(feedUriAll)
expect(paginatedAll.map((fg) => fg.uri)).not.toContain(feedUriPrime) // taken-down
expect(forSnapshot(paginatedAll)).toMatchSnapshot()
})
Expand Down Expand Up @@ -490,7 +517,7 @@ describe('feed generation', () => {
expect(res.data.feeds.map((f) => f.uri)).toEqual([
feedUriAll,
feedUriEven,
feedUriBadPagination,
feedUriBadPaginationLimit,
])
})

Expand Down Expand Up @@ -605,7 +632,7 @@ describe('feed generation', () => {

it('paginates, handling feed not respecting limit.', async () => {
const res = await agent.api.app.bsky.feed.getFeed(
{ feed: feedUriBadPagination, limit: 3 },
{ feed: feedUriBadPaginationLimit, limit: 3 },
{
headers: await network.serviceHeaders(
alice,
Expand Down Expand Up @@ -640,6 +667,22 @@ describe('feed generation', () => {
})
})

it('returns empty cursor with feeds that echo back the same cursor from the param.', async () => {
const res = await agent.api.app.bsky.feed.getFeed(
{ feed: feedUriBadPaginationCursor, cursor: '1', limit: 2 },
{
headers: await network.serviceHeaders(
alice,
ids.AppBskyFeedGetFeed,
gen.did,
),
},
)

expect(res.data.cursor).toBeUndefined()
expect(res.data.feed).toHaveLength(2)
})

it('resolves contents of taken-down feed.', async () => {
const tryGetFeed = agent.api.app.bsky.feed.getFeed(
{ feed: feedUriPrime },
Expand Down Expand Up @@ -712,7 +755,13 @@ describe('feed generation', () => {

const feedGenHandler =
(
feedName: 'even' | 'all' | 'prime' | 'bad-pagination' | 'needs-auth',
feedName:
| 'even'
| 'all'
| 'prime'
| 'bad-pagination-limit'
| 'bad-pagination-cursor'
| 'needs-auth',
): SkeletonHandler =>
async ({ req, params }) => {
if (feedName === 'needs-auth' && !req.headers.authorization) {
Expand Down Expand Up @@ -753,17 +802,22 @@ describe('feed generation', () => {
return true
})
const feedResults =
feedName === 'bad-pagination'
feedName === 'bad-pagination-limit'
? fullFeed.slice(offset) // does not respect limit
: fullFeed.slice(offset, offset + limit)
const lastResult = feedResults.at(-1)
const cursorResult =
feedName === 'bad-pagination-cursor'
? cursor
: lastResult
? (fullFeed.indexOf(lastResult) + 1).toString()
: undefined

return {
encoding: 'application/json',
body: {
feed: feedResults,
cursor: lastResult
? (fullFeed.indexOf(lastResult) + 1).toString()
: undefined,
cursor: cursorResult,
$auth: jwtBody(req.headers.authorization), // for testing purposes
},
}
Expand Down

0 comments on commit 2d9a2a8

Please sign in to comment.