fix: backfill symbol_demand for sidebar-added symbols + analyst ratings schema fix
- Add await ctx.cache.subscribe() to addSymbol mutation so symbols added via the sidebar get registered in symbol_demand and yfinance jobs are queued immediately - Backfill PEP, WYNN, STZ, CELH into symbol_demand + adapter_queue - Upgrade yahoo-finance2 3.15.3 -> 3.15.4 and pass validateResult:false to quoteSummary() to handle Yahoo schema drift - Add error detail logging for analyst ratings schema failures - Update .gitignore with common ignores
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
// Tests — ReportRunner (Slice 22). Pure, no network.
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { generateReport } from '../ReportRunner.ts';
|
||||
|
||||
test('generateReport produces valid HTML for a symbol report', () => {
|
||||
const result = generateReport({ scope: 'symbol', symbol: 'NVDA', data: { overview: 'NVDA overview text.' } });
|
||||
assert.ok(result.html.startsWith('<!DOCTYPE html>'));
|
||||
assert.ok(result.html.includes('NVDA'));
|
||||
assert.ok(result.html.includes('Overview'));
|
||||
assert.ok(result.html.includes('Educational analysis'));
|
||||
assert.ok(result.html.includes('</html>'));
|
||||
assert.equal(result.title, 'Research Note: NVDA');
|
||||
});
|
||||
|
||||
test('generateReport produces valid HTML for portfolio report', () => {
|
||||
const result = generateReport({
|
||||
scope: 'portfolio',
|
||||
data: {
|
||||
holdings: [
|
||||
{ symbol: 'NVDA', shares: 10, avgCost: 250 },
|
||||
{ symbol: 'AAPL', shares: 5, avgCost: 180 },
|
||||
],
|
||||
},
|
||||
});
|
||||
assert.ok(result.html.includes('Holdings'));
|
||||
assert.ok(result.html.includes('NVDA'));
|
||||
assert.ok(result.html.includes('AAPL'));
|
||||
assert.ok(result.html.includes('$2500')); // 10 * 250
|
||||
assert.equal(result.title, 'Research Note: Portfolio Analysis');
|
||||
});
|
||||
|
||||
test('generateReport includes recommended actions when provided', () => {
|
||||
const result = generateReport({
|
||||
scope: 'risk_posture',
|
||||
data: {
|
||||
recommendedActions: [
|
||||
{ id: 'consider_reducing_position', tradeOff: 'Test trade-off', explanation: 'Test explanation.' },
|
||||
],
|
||||
},
|
||||
});
|
||||
assert.ok(result.html.includes('Considerations'));
|
||||
assert.ok(result.html.includes('Test trade-off'));
|
||||
assert.ok(result.html.includes('Test explanation'));
|
||||
});
|
||||
|
||||
test('generateReport includes ADR-0007 footer', () => {
|
||||
const result = generateReport({ scope: 'symbol', symbol: 'TEST', data: {} });
|
||||
assert.ok(result.html.includes('not investment advice'));
|
||||
assert.ok(result.html.includes('responsible for your own decisions'));
|
||||
});
|
||||
|
||||
test('generateReport outputs deterministic content given same inputs', () => {
|
||||
const input = { scope: 'symbol' as const, symbol: 'NVDA', data: { overview: 'test' } };
|
||||
const a = generateReport(input);
|
||||
const b = generateReport(input);
|
||||
assert.equal(a.html, b.html);
|
||||
assert.equal(a.title, b.title);
|
||||
});
|
||||
|
||||
test('generateReport escapes HTML in user data', () => {
|
||||
const result = generateReport({ scope: 'symbol', symbol: '<script>', data: { overview: '<script>alert("xss")</script>' } });
|
||||
assert.ok(!result.html.includes('<script>'));
|
||||
assert.ok(result.html.includes('<script>'));
|
||||
});
|
||||
Reference in New Issue
Block a user