import { test } from 'node:test'; import assert from 'node:assert/strict'; import { StockScorer } from '../server/scorers/StockScorer'; import type { StockMetrics } from '../server/types'; const baseRules = { gates: { maxDebtToEquity: 3.0, minQuickRatio: 0.5, maxPERatio: 20, maxPegGate: 1.5 }, weights: { margin: 2, opMargin: 2, roe: 3, peg: 2, revenue: 2, fcf: 2 }, thresholds: { marginHigh: 20, marginMed: 10, opMarginHigh: 20, opMarginMed: 10, roeHigh: 20, roeMed: 10, pegHigh: 1.0, pegMed: 1.5, revHigh: 15, revMed: 5, fcfHigh: 5, fcfMed: 2, }, }; // Minimal fixture — tests exercise specific fields; unused metrics are null. const nullMetrics: Omit< StockMetrics, | 'sector' | 'capCategory' | 'growthCategory' | 'currentPrice' | 'peRatio' | 'pegRatio' | 'debtToEquity' | 'quickRatio' | 'returnOnEquity' | 'operatingMargin' | 'netProfitMargin' | 'revenueGrowth' | 'fcfYield' > = { priceToBook: null, grossMargin: null, earningsGrowth: null, pFFO: null, dividendYield: null, beta: null, week52High: null, week52Low: null, week52Change: null, week52FromHigh: null, week52FromLow: null, marketCap: null, analystRating: null, analystTargetPrice: null, analystUpside: null, numberOfAnalysts: null, dcfIntrinsicValue: null, dcfMarginOfSafety: null, }; const pass: StockMetrics = { ...nullMetrics, sector: 'GENERAL', capCategory: 'Large Cap', growthCategory: 'Growth', currentPrice: 150, peRatio: 15, pegRatio: 1.2, debtToEquity: 1.0, quickRatio: 1.0, returnOnEquity: 22, operatingMargin: 25, netProfitMargin: 18, revenueGrowth: 16, fcfYield: 6, }; test('rejects on high D/E', () => { const result = StockScorer.score({ ...pass, debtToEquity: 4.0 }, baseRules); assert.equal(result.label, '🔴 REJECT'); assert(result.scoreSummary.includes('D/E')); }); test('rejects on high P/E', () => { const result = StockScorer.score({ ...pass, peRatio: 25 }, baseRules); assert.equal(result.label, '🔴 REJECT'); assert(result.scoreSummary.includes('P/E')); }); test('rejects on high PEG', () => { const result = StockScorer.score({ ...pass, pegRatio: 2.0 }, baseRules); assert.equal(result.label, '🔴 REJECT'); }); test('skips gate when metric is null (missing data)', () => { const result = StockScorer.score({ ...pass, pegRatio: null, peRatio: null }, baseRules); assert.notEqual(result.label, '🔴 REJECT'); }); test('high-conviction BUY on strong metrics', () => { const result = StockScorer.score(pass, baseRules); assert.equal(result.label, '🟢 BUY (High Conviction)'); }); test('audit breakdown contains scored factors', () => { const result = StockScorer.score(pass, baseRules); assert(result.audit.passedGates); assert(result.audit.breakdown!.roe != null); assert(result.audit.breakdown!.margin != null); }); test('beta > 1.5 surfaces as risk flag', () => { const result = StockScorer.score({ ...pass, beta: 2.0 }, baseRules); assert(result.audit.riskFlags?.some((f) => f.includes('High volatility'))); }); test('near 52-week high surfaces as risk flag', () => { const result = StockScorer.score( { ...pass, week52High: 200, week52Low: 100, currentPrice: 195 }, baseRules, ); assert(result.audit.riskFlags?.some((f) => f.includes('52-week high'))); });