test: expand user-tweets coverage

This commit is contained in:
Peter Steinberger
2026-01-12 01:45:41 +00:00
parent 64b0460c4d
commit 11cc9fbd27
2 changed files with 84 additions and 0 deletions
+29
View File
@@ -284,6 +284,35 @@ d('live CLI (Twitter/X)', () => {
expect(tweets[0].author?.username?.toLowerCase()).toBe(testHandle.toLowerCase()); expect(tweets[0].author?.username?.toLowerCase()).toBe(testHandle.toLowerCase());
}); });
it('user-tweets paged JSON returns { tweets, nextCursor }', async () => {
const testHandle =
process.env.BIRD_LIVE_USER_TWEETS_PAGED_HANDLE ?? process.env.BIRD_LIVE_USER_TWEETS_HANDLE ?? 'X';
const userTweets = await runBird(
[
...baseArgs,
'--cookie-timeout',
cookieTimeoutArg,
'user-tweets',
testHandle,
'-n',
'50',
'--max-pages',
'3',
'--delay',
'0',
'--json',
],
{
timeoutMs: 45_000,
},
);
expect(userTweets.exitCode).toBe(0);
const payload = parseJson<{ tweets?: Array<{ id?: string }>; nextCursor?: string | null }>(userTweets.stdout);
expect(Array.isArray(payload.tweets)).toBe(true);
expect(payload.tweets?.length).toBeGreaterThan(0);
expect(payload.tweets?.[0]?.id?.length).toBeGreaterThan(0);
});
it('query-ids returns JSON', async () => { it('query-ids returns JSON', async () => {
const queryIds = await runBird([...baseArgs, '--cookie-timeout', cookieTimeoutArg, 'query-ids', '--json'], { const queryIds = await runBird([...baseArgs, '--cookie-timeout', cookieTimeoutArg, 'query-ids', '--json'], {
timeoutMs: 60_000, timeoutMs: 60_000,
+55
View File
@@ -394,6 +394,61 @@ describe('TwitterClient getUserTweetsPaged', () => {
global.fetch = mockFetch as unknown as typeof fetch; global.fetch = mockFetch as unknown as typeof fetch;
}); });
it('requests only the remaining count', async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () =>
makeTimelineResponse(
[makeTweetResult('1', 'Tweet 1'), makeTweetResult('2', 'Tweet 2'), makeTweetResult('3', 'Tweet 3')],
'cursor-2',
),
});
const client = new TwitterClient({ cookies: validCookies });
const result = await client.getUserTweetsPaged('12345', 5, { pageDelayMs: 0 });
expect(result.success).toBe(true);
const [url] = mockFetch.mock.calls[0];
const variablesRaw = new URL(url).searchParams.get('variables');
expect(variablesRaw).not.toBeNull();
const variables = JSON.parse(String(variablesRaw)) as { count?: number };
expect(variables.count).toBe(5);
});
it('enforces a hard 10 page cap by default', async () => {
for (let page = 1; page <= 10; page += 1) {
const startId = (page - 1) * 20 + 1;
const tweets = Array.from({ length: 20 }, (_v, index) => {
const id = String(startId + index);
return makeTweetResult(id, `Tweet ${id}`);
});
mockFetch.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => makeTimelineResponse(tweets, `cursor-${page + 1}`),
});
}
const client = new TwitterClient({ cookies: validCookies });
const result = await client.getUserTweetsPaged('12345', 9999, { pageDelayMs: 0 });
expect(result.success).toBe(true);
expect(mockFetch).toHaveBeenCalledTimes(10);
expect(result.tweets?.length).toBe(200);
expect(result.nextCursor).toBe('cursor-11');
});
it('returns error for invalid limit', async () => {
const client = new TwitterClient({ cookies: validCookies });
const result = await client.getUserTweetsPaged('12345', 0);
expect(result.success).toBe(false);
expect(result.error).toContain('Invalid limit');
expect(mockFetch).not.toHaveBeenCalled();
});
it('fetches multiple pages with cursor', async () => { it('fetches multiple pages with cursor', async () => {
mockFetch mockFetch
.mockResolvedValueOnce({ .mockResolvedValueOnce({