From ad125348d67951898219f68f7c89148d8baeac5d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 11 Jan 2026 10:58:47 +0000 Subject: [PATCH] test: expand lists feature coverage --- tests/runtime-features.test.ts | 6 +++ tests/twitter-client.lists.test.ts | 62 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/tests/runtime-features.test.ts b/tests/runtime-features.test.ts index 018144d..4bd024f 100644 --- a/tests/runtime-features.test.ts +++ b/tests/runtime-features.test.ts @@ -29,6 +29,9 @@ describe('runtime-features', () => { likes: { likes_only: true, }, + lists: { + responsive_web_text_conversations_enabled: true, + }, }, }); @@ -41,6 +44,9 @@ describe('runtime-features', () => { const likes = buildLikesFeatures(); expect(likes.global_flag).toBe(true); expect(likes.likes_only).toBe(true); + + const lists = buildListsFeatures(); + expect(lists.responsive_web_text_conversations_enabled).toBe(true); }); it('refresh merges defaults with existing cache', async () => { diff --git a/tests/twitter-client.lists.test.ts b/tests/twitter-client.lists.test.ts index ddeafa3..e5407e5 100644 --- a/tests/twitter-client.lists.test.ts +++ b/tests/twitter-client.lists.test.ts @@ -87,6 +87,45 @@ describe('TwitterClient lists', () => { expect(result.lists?.[0].owner?.username).toBe('testuser'); }); + it('includes required feature flags in list requests', async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + data: { + user: { + result: { + timeline: { + timeline: { + instructions: [], + }, + }, + }, + }, + }, + }), + }); + + const client = new TwitterClient({ cookies: validCookies }); + const clientPrivate = client as unknown as TwitterClientPrivate; + clientPrivate.getCurrentUser = async () => ({ + success: true, + user: { id: '12345', username: 'testuser', name: 'Test User' }, + }); + clientPrivate.getListOwnershipsQueryIds = async () => ['test']; + + await client.getOwnedLists(1); + + const [url] = mockFetch.mock.calls[0]; + const parsedFeatures = JSON.parse(new URL(url as string).searchParams.get('features') as string); + expect(parsedFeatures.responsive_web_graphql_exclude_directive_enabled).toBe(true); + expect(parsedFeatures.blue_business_profile_image_shape_enabled).toBe(false); + expect(parsedFeatures.responsive_web_text_conversations_enabled).toBe(false); + expect(parsedFeatures.tweetypie_unmention_optimization_enabled).toBe(true); + expect(parsedFeatures.vibe_api_enabled).toBe(false); + expect(parsedFeatures.interactive_text_enabled).toBe(false); + }); + it('handles private lists correctly', async () => { mockFetch.mockResolvedValueOnce({ ok: true, @@ -326,6 +365,29 @@ describe('TwitterClient lists', () => { expect(result.error).toContain('Rate limit exceeded'); }); + it('returns missing feature flag errors from the API', async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + errors: [{ message: 'missing required feature flag: responsive_web_graphql_exclude_directive_enabled' }], + }), + }); + + const client = new TwitterClient({ cookies: validCookies }); + const clientPrivate = client as unknown as TwitterClientPrivate; + clientPrivate.getCurrentUser = async () => ({ + success: true, + user: { id: '12345', username: 'testuser', name: 'Test User' }, + }); + clientPrivate.getListOwnershipsQueryIds = async () => ['test']; + + const result = await client.getOwnedLists(1); + + expect(result.success).toBe(false); + expect(result.error).toContain('missing required feature flag'); + }); + it('retries on 404 error after refreshing query IDs', async () => { mockFetch .mockResolvedValueOnce({