CI / build + unit (pull_request) Successful in 1m15s
#rss subcommands on the command channel: help / add <url> <name> / remove <name> / tags / sources / get <name|#tag>. Sources are stored in a new rss_sources table in the existing MESH_DB_PATH file (second node:sqlite connection, case-insensitive names); feeds parsed with fast-xml-parser (RSS 2.0 + Atom); AI tags and headline summaries via injected LlmService.chat() with bounded maxTokens (50/200). CommandService gains prefix registration so one handler owns the whole #rss family while #ping stays an exact match; wired in CommandModule's factory — still no constructor-level cycle, no forwardRef. One dead feed does not sink a tag summary.
65 lines
1.9 KiB
TypeScript
65 lines
1.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { parseFeed } from './rss.feed.js';
|
|
|
|
const RSS2 = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rss version="2.0">
|
|
<channel>
|
|
<title>Some Feed</title>
|
|
<item>
|
|
<title>First headline</title>
|
|
<link>https://example.com/first</link>
|
|
</item>
|
|
<item>
|
|
<title><![CDATA[Second: with CDATA]]></title>
|
|
<description>ignored</description>
|
|
</item>
|
|
<item>
|
|
<title></title>
|
|
<link>https://example.com/empty-title</link>
|
|
</item>
|
|
</channel>
|
|
</rss>`;
|
|
|
|
const ATOM = `<?xml version="1.0" encoding="utf-8"?>
|
|
<feed xmlns="http://www.w3.org/2005/Atom">
|
|
<title>An Atom feed</title>
|
|
<entry>
|
|
<title>Only entry</title>
|
|
<link rel="alternate" href="https://example.com/atom"/>
|
|
</entry>
|
|
</feed>`;
|
|
|
|
describe('parseFeed', () => {
|
|
it('parses RSS 2.0 items in document order, keeping links where present and skipping empty titles', () => {
|
|
expect(parseFeed(RSS2)).toEqual([
|
|
{ title: 'First headline', link: 'https://example.com/first' },
|
|
{ title: 'Second: with CDATA' },
|
|
]);
|
|
});
|
|
|
|
it('parses Atom entries (single entry object, link as href attribute)', () => {
|
|
expect(parseFeed(ATOM)).toEqual([
|
|
{ title: 'Only entry', link: 'https://example.com/atom' },
|
|
]);
|
|
});
|
|
|
|
it('returns [] for malformed XML', () => {
|
|
expect(parseFeed('this is not xml at all')).toEqual([]);
|
|
expect(parseFeed('<rss><channel><item><title>broken')).toEqual([]);
|
|
});
|
|
|
|
it('returns [] when there are no items or none with a usable title', () => {
|
|
expect(
|
|
parseFeed('<rss version="2.0"><channel><title>x</title></channel></rss>'),
|
|
).toEqual([]);
|
|
expect(
|
|
parseFeed('<feed xmlns="http://www.w3.org/2005/Atom"></feed>'),
|
|
).toEqual([]);
|
|
expect(
|
|
parseFeed(
|
|
'<rss version="2.0"><channel><item><title> </title></item></channel></rss>',
|
|
),
|
|
).toEqual([]);
|
|
});
|
|
});
|