Files
2026-06-11 19:18:19 -04:00

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 &amp; B say &quot;hi&quot;</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');
});
});