fix: tighten news output and parsing (#39) (thanks @aavetis)
This commit is contained in:
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { CliContext } from '../src/cli/shared.js';
|
||||
import { registerNewsCommand } from '../src/commands/news.js';
|
||||
import { TwitterClient } from '../src/lib/twitter-client.js';
|
||||
|
||||
describe('news command', () => {
|
||||
let program: Command;
|
||||
@@ -151,4 +152,36 @@ describe('news command', () => {
|
||||
errorSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('limits related tweets to tweets-per-item in CLI output', async () => {
|
||||
registerNewsCommand(program, mockContext as CliContext);
|
||||
const getNewsSpy = vi.spyOn(TwitterClient.prototype, 'getNews').mockResolvedValue({
|
||||
success: true,
|
||||
items: [
|
||||
{
|
||||
id: 'item-1',
|
||||
headline: 'News headline',
|
||||
category: 'News',
|
||||
tweets: [
|
||||
{ id: 't1', text: 'first tweet', author: { username: 'a', name: 'A' } },
|
||||
{ id: 't2', text: 'second tweet', author: { username: 'b', name: 'B' } },
|
||||
{ id: 't3', text: 'third tweet', author: { username: 'c', name: 'C' } },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
try {
|
||||
await program.parseAsync(['node', 'bird', 'news', '--with-tweets', '--tweets-per-item', '2', '--count', '1']);
|
||||
expect(getNewsSpy).toHaveBeenCalledWith(1, expect.objectContaining({ tweetsPerItem: 2, withTweets: true }));
|
||||
const tweetLines = logSpy.mock.calls
|
||||
.map((call) => String(call[0]))
|
||||
.filter((line) => line.trimStart().startsWith('@'));
|
||||
expect(tweetLines).toHaveLength(2);
|
||||
} finally {
|
||||
getNewsSpy.mockRestore();
|
||||
logSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ const makeResponse = (overrides: Partial<ResponseLike> = {}): ResponseLike => ({
|
||||
});
|
||||
|
||||
// Helper to create GenericTimelineById response structure
|
||||
const makeTimelineResponse = (items: any[]) => ({
|
||||
const makeTimelineResponse = (items: unknown[]) => ({
|
||||
data: {
|
||||
timeline: {
|
||||
timeline: {
|
||||
@@ -50,6 +50,21 @@ const makeTimelineResponse = (items: any[]) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const makeTimelineResponseWithInstruction = (type: string, entries: unknown[]) => ({
|
||||
data: {
|
||||
timeline: {
|
||||
timeline: {
|
||||
instructions: [
|
||||
{
|
||||
type,
|
||||
entries,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('TwitterClient news API coverage', () => {
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
@@ -162,9 +177,7 @@ describe('TwitterClient news API coverage', () => {
|
||||
});
|
||||
|
||||
it('returns error when no news items found', async () => {
|
||||
const mockFetch = vi
|
||||
.fn()
|
||||
.mockResolvedValue(makeResponse({ json: async () => makeTimelineResponse([]) }));
|
||||
const mockFetch = vi.fn().mockResolvedValue(makeResponse({ json: async () => makeTimelineResponse([]) }));
|
||||
|
||||
global.fetch = mockFetch as unknown as typeof fetch;
|
||||
|
||||
@@ -244,5 +257,70 @@ describe('TwitterClient news API coverage', () => {
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.items?.length).toBe(2);
|
||||
});
|
||||
|
||||
it('parses entries from non-TimelineAddEntries instructions', async () => {
|
||||
const mockFetch = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
makeResponse({
|
||||
json: async () =>
|
||||
makeTimelineResponseWithInstruction('TimelineReplaceEntry', [
|
||||
{
|
||||
entryId: 'replace-entry-1',
|
||||
content: {
|
||||
itemContent: {
|
||||
is_ai_trend: true,
|
||||
name: 'Replacement headline',
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
}),
|
||||
)
|
||||
.mockResolvedValue(makeResponse({ json: async () => makeTimelineResponse([]) }));
|
||||
|
||||
global.fetch = mockFetch as unknown as typeof fetch;
|
||||
|
||||
const client = new TwitterClient({ cookies: validCookies });
|
||||
const result = await client.getNews(1);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.items?.[0].headline).toBe('Replacement headline');
|
||||
});
|
||||
|
||||
it('assigns unique ids for module entries', async () => {
|
||||
const mockFetch = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
makeResponse({
|
||||
json: async () =>
|
||||
makeTimelineResponseWithInstruction('TimelineAddEntries', [
|
||||
{
|
||||
entryId: 'module-entry',
|
||||
content: {
|
||||
items: [
|
||||
{
|
||||
item: { itemContent: { is_ai_trend: true, name: 'Headline A' } },
|
||||
},
|
||||
{
|
||||
item: { itemContent: { is_ai_trend: true, name: 'Headline B' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]),
|
||||
}),
|
||||
)
|
||||
.mockResolvedValue(makeResponse({ json: async () => makeTimelineResponse([]) }));
|
||||
|
||||
global.fetch = mockFetch as unknown as typeof fetch;
|
||||
|
||||
const client = new TwitterClient({ cookies: validCookies });
|
||||
const result = await client.getNews(2);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
const ids = result.items?.map((item) => item.id) ?? [];
|
||||
expect(new Set(ids).size).toBe(ids.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user