import { test } from 'node:test'; import assert from 'node:assert/strict'; import { EtfScorer } from '../server/scorers/EtfScorer'; import type { EtfMetrics } from '../server/types'; const rules = { gates: { maxExpenseRatio: 0.5 }, weights: { yield: 2, lowCost: 3 }, thresholds: { minYield: 1.5, maxExpense: 0.1, minVolume: 500000 }, }; // Helper to build minimal EtfMetrics fixtures (totalAssets/fiveYearReturn unused by scorer). const etf = (partial: Partial): EtfMetrics => ({ totalAssets: 0, fiveYearReturn: 0, volume: 0, yield: 0, expenseRatio: 0, ...partial, }); test('rejects ETF with expense ratio above gate', () => { const result = EtfScorer.score(etf({ expenseRatio: 0.8, yield: 2.0 }), rules); assert.equal(result.label, '🔴 REJECT'); }); test('efficient label for low-cost, high-yield ETF', () => { const result = EtfScorer.score(etf({ expenseRatio: 0.03, yield: 2.0, volume: 1000000 }), rules); assert.equal(result.label, '🟢 Efficient'); }); test('neutral when yield is below threshold', () => { const result = EtfScorer.score(etf({ expenseRatio: 0.03, yield: 0.4, volume: 1000000 }), rules); assert.equal(result.label, '🟡 Neutral'); }); test('audit breakdown includes cost, yield, vol keys', () => { const result = EtfScorer.score(etf({ expenseRatio: 0.03, yield: 2.0, volume: 1000000 }), rules); assert(result.audit.breakdown!.cost != null); assert(result.audit.breakdown!.yield != null); assert(result.audit.breakdown!.vol != null); }); test('penalises ETF with volume below liquidity floor', () => { const result = EtfScorer.score(etf({ expenseRatio: 0.03, yield: 2.0, volume: 100000 }), rules); assert(result.audit.breakdown!.vol < 0, 'low-volume ETF should receive negative vol score'); }); test('scores 5Y return when threshold configured', () => { const rulesWithReturn = { ...rules, weights: { ...rules.weights, fiveYearReturn: 2 }, thresholds: { ...rules.thresholds, minFiveYearReturn: 8.0 }, }; const good = EtfScorer.score( etf({ expenseRatio: 0.03, yield: 2.0, volume: 1000000, fiveYearReturn: 10 }), rulesWithReturn, ); const poor = EtfScorer.score( etf({ expenseRatio: 0.03, yield: 2.0, volume: 1000000, fiveYearReturn: 5 }), rulesWithReturn, ); assert(good.audit.breakdown!.fiveYearReturn > 0, 'strong 5Y return should score positively'); assert(poor.audit.breakdown!.fiveYearReturn < 0, 'weak 5Y return should score negatively'); });