86 lines
4.0 KiB
TypeScript
86 lines
4.0 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { EdgarPoller } from '../server/domains/news/pollers/EdgarPoller.js';
|
|
import { PrWirePoller } from '../server/domains/news/pollers/PrWirePoller.js';
|
|
import { RssParser } from '../server/domains/news/rss.js';
|
|
import { noopLogger } from '../server/domains/shared/utils/logger.js';
|
|
|
|
const EDGAR_ATOM = `<?xml version="1.0" encoding="ISO-8859-1" ?>
|
|
<feed xmlns="http://www.w3.org/2005/Atom">
|
|
<title>Latest Filings</title>
|
|
<entry>
|
|
<title>8-K - APPLE INC (0000320193) (Filer)</title>
|
|
<link rel="alternate" type="text/html" href="https://www.sec.gov/Archives/edgar/data/320193/000032019326000001-index.htm"/>
|
|
<updated>2026-06-09T13:01:02-04:00</updated>
|
|
<id>urn:tag:sec.gov,2008:accession-number=0000320193-26-000001</id>
|
|
</entry>
|
|
<entry>
|
|
<title>8-K - UNKNOWN CO (0009999999) (Filer)</title>
|
|
<link rel="alternate" type="text/html" href="https://www.sec.gov/Archives/edgar/data/9999999/x-index.htm"/>
|
|
<updated>2026-06-09T13:05:00-04:00</updated>
|
|
<id>urn:tag:sec.gov,2008:accession-number=x</id>
|
|
</entry>
|
|
</feed>`;
|
|
|
|
const PRWIRE_RSS = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<rss version="2.0"><channel>
|
|
<item>
|
|
<title>Acme Corp (NYSE: ACME) Announces Record Q2 Results</title>
|
|
<link>https://www.example.com/acme-q2</link>
|
|
<pubDate>Tue, 09 Jun 2026 12:00:00 GMT</pubDate>
|
|
<description><![CDATA[Acme Corp (NYSE: ACME) and partner Beta Inc (Nasdaq: BETA) today announced...]]></description>
|
|
</item>
|
|
<item>
|
|
<title>Local bakery wins award</title>
|
|
<link>https://www.example.com/bakery</link>
|
|
<pubDate>Tue, 09 Jun 2026 11:00:00 GMT</pubDate>
|
|
<description>No public companies here.</description>
|
|
</item>
|
|
</channel></rss>`;
|
|
|
|
test('news pollers', async (t) => {
|
|
await t.test('EdgarPoller maps CIK to ticker and filters by universe', () => {
|
|
const poller = new EdgarPoller(noopLogger, 'test-agent');
|
|
poller.setTickerMap(new Map([['0000320193', 'AAPL']]));
|
|
|
|
const stories = poller.parseFeed(EDGAR_ATOM, '8-K', 'regulatory', new Set(['AAPL']));
|
|
assert.equal(stories.length, 1); // unknown CIK dropped
|
|
assert.deepEqual(stories[0].tickers, ['AAPL']);
|
|
assert.equal(stories[0].source, 'edgar');
|
|
assert.equal(stories[0].catalystHint, 'regulatory');
|
|
assert.ok(stories[0].headline.startsWith('8-K filing:'));
|
|
assert.ok(stories[0].headline.includes('APPLE INC'));
|
|
assert.ok(stories[0].url.includes('sec.gov'));
|
|
});
|
|
|
|
await t.test('EdgarPoller drops universe misses', () => {
|
|
const poller = new EdgarPoller(noopLogger, 'test-agent');
|
|
poller.setTickerMap(new Map([['0000320193', 'AAPL']]));
|
|
const stories = poller.parseFeed(EDGAR_ATOM, '8-K', 'regulatory', new Set(['MSFT']));
|
|
assert.equal(stories.length, 0);
|
|
});
|
|
|
|
await t.test('PrWirePoller extracts exchange-tagged tickers', () => {
|
|
const stories = PrWirePoller.parseFeed(PRWIRE_RSS);
|
|
assert.equal(stories.length, 1); // bakery story has no tickers → skipped
|
|
assert.deepEqual(stories[0].tickers.sort(), ['ACME', 'BETA']);
|
|
assert.equal(stories[0].source, 'prwire');
|
|
assert.ok(stories[0].publishedAt.startsWith('2026-06-09'));
|
|
});
|
|
|
|
await t.test('extractTickers handles exchange tag variants', () => {
|
|
assert.deepEqual(PrWirePoller.extractTickers('(NYSE: ABC)'), ['ABC']);
|
|
assert.deepEqual(PrWirePoller.extractTickers('(Nasdaq: xyz)'), ['XYZ']);
|
|
assert.deepEqual(PrWirePoller.extractTickers('(NYSE American: BRK.B)'), ['BRK.B']);
|
|
assert.deepEqual(PrWirePoller.extractTickers('(OTCQB: TINY)'), ['TINY']);
|
|
assert.deepEqual(PrWirePoller.extractTickers('no tags here'), []);
|
|
});
|
|
|
|
await t.test('RssParser decodes entities and strips CDATA', () => {
|
|
const block = '<item><title>A & B say "hi"</title></item>';
|
|
assert.equal(RssParser.tag(block, 'title'), 'A & B say "hi"');
|
|
const cdata = '<item><description><![CDATA[Text <b>bold</b> here]]></description></item>';
|
|
assert.equal(RssParser.tag(cdata, 'description'), 'Text bold here');
|
|
});
|
|
});
|