pulse/cli.test.ts
Edo Limburg c79eb6d76d Add CLI entry point, RSS content extraction, and image support
Features:
- Add CLI with commands: start, add, remove, list, fetch, status, items
- Auto-detect RSS format when adding feeds
- Auto-run database migrations on startup
- Extract full HTML content from RSS description field (NOS-style feeds)
- Extract image URLs from RSS enclosure tags
- Display images in terminal output with emoji
- Include imageUrl in JSON formatter output

Database:
- Add image_url column to feed_items table
- Update storage layer to persist imageUrl field

Tests:
- Add 10 CLI integration tests
- Add 3 RSS parser tests for image/content extraction
- Add 2 storage tests for imageUrl persistence

Dependencies:
- Add commander for CLI framework

All 144 tests passing
2026-05-05 23:05:30 +02:00

128 lines
3.3 KiB
TypeScript

/**
* CLI tests.
* Tests the Pulse CLI commands.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { execSync } from 'child_process';
import { existsSync, unlinkSync } from 'fs';
import { join } from 'path';
const TEST_DB_PATH = join(process.cwd(), 'test-cli.db');
const CLI_CMD = 'npx tsx cli.ts';
function runCli(args: string): string {
const env = {
...process.env,
PULSE_DATABASE_TYPE: 'sqlite',
PULSE_SQLITE_PATH: TEST_DB_PATH,
};
try {
return execSync(`${CLI_CMD} ${args}`, {
encoding: 'utf-8',
env,
stdio: 'pipe',
});
} catch (error) {
if (error instanceof Error && 'stdout' in error && error.stdout) {
return error.stdout as string;
}
throw error;
}
}
describe('CLI', () => {
beforeEach(() => {
// Clean up test database before each test
if (existsSync(TEST_DB_PATH)) {
unlinkSync(TEST_DB_PATH);
}
});
afterEach(() => {
// Clean up test database after each test
if (existsSync(TEST_DB_PATH)) {
unlinkSync(TEST_DB_PATH);
}
});
describe('help', () => {
it('should display help', () => {
const output = runCli('--help');
expect(output).toContain('Usage: pulse');
expect(output).toContain('Commands:');
expect(output).toContain('start');
expect(output).toContain('add');
expect(output).toContain('list');
});
it('should display version', () => {
const output = runCli('--version');
expect(output.trim()).toBe('0.1.0');
});
});
describe('list', () => {
it('should show empty list when no feeds', () => {
const output = runCli('list');
expect(output).toContain('No feed sources configured');
});
});
describe('add', () => {
it('should add a feed source', () => {
const output = runCli('add "https://example.com/feed.xml" --name "Test Feed" --format rss');
expect(output).toContain('Added feed:');
expect(output).toContain('https://example.com/feed.xml');
expect(output).toContain('rss');
});
it('should reject invalid format', () => {
expect(() => {
runCli('add "https://example.com/feed.xml" --format invalid');
}).toThrow();
});
});
describe('remove', () => {
it('should remove a feed source', () => {
// First add a feed
runCli('add "https://example.com/feed.xml" --name "Test Feed" --format rss');
// Then remove it
const output = runCli('remove 9bb0a00b');
expect(output).toContain('Removed feed:');
});
it('should error when removing non-existent feed', () => {
expect(() => {
runCli('remove non-existent-id');
}).toThrow();
});
});
describe('status', () => {
it('should show status for no feeds', () => {
const output = runCli('status');
expect(output).toContain('No feed sources configured');
});
it('should show status for healthy feed', () => {
// Add a feed
runCli('add "https://example.com/feed.xml" --name "Test Feed" --format rss');
const output = runCli('status');
expect(output).toContain('Healthy');
expect(output).toContain('Test Feed');
});
});
describe('items', () => {
it('should show no items when database is empty', () => {
const output = runCli('items');
expect(output).toContain('No items found');
});
});
});