import test from 'node:test'; import assert from 'node:assert/strict'; import { CallsController } from '../server/domains/calls/calls.controller.js'; import { CalendarService } from '../server/domains/calls/CalendarService.js'; import type { MarketCall } from '../server/domains/shared/types/calls.model.js'; class MockMarketCallRepository { private calls: (MarketCall & { id: string })[] = [ { id: '1', title: 'AAPL Post-Earnings', quarter: 'Q2 2024', thesis: 'Strong iPhone sales cycle', tickers: ['AAPL'], date: '2024-05-01', snapshot: {}, }, ]; async list(): Promise<(MarketCall & { id: string })[]> { return this.calls.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); } async get(id: string): Promise<(MarketCall & { id: string }) | null> { return this.calls.find((c) => c.id === id) || null; } async create(call: MarketCall): Promise { const id = String(this.calls.length + 1); const newCall = { ...call, id }; this.calls.push(newCall); return newCall; } async delete(id: string) { const index = this.calls.findIndex((c) => c.id === id); if (index >= 0) { this.calls.splice(index, 1); return true; } return false; } } class MockScreenerEngine { async screenTickers(tickers: string[]) { return { STOCK: tickers.map((ticker) => ({ asset: { ticker, type: 'STOCK', currentPrice: 100, metrics: {}, }, signal: 'STRONG_BUY', })), ETF: [], BOND: [], ERROR: [], marketContext: { sp500Price: 5500, riskFreeRate: 4.5, vixLevel: 12.5, rateRegime: 'NORMAL' as const, volatilityRegime: 'LOW' as const, benchmarks: { marketPE: 20, techPE: 28, reitYield: 3.5, igSpread: 1.2, }, }, }; } } class MockYahooClient { async fetchCalendarEvents() { return [ { ticker: 'AAPL', date: new Date('2024-05-15'), type: 'earnings', epsEstimate: 1.52, epsActual: null, }, ]; } } test('CallsController', async (t) => { await t.test('registers GET /api/calls endpoint', async () => { const repository = new MockMarketCallRepository() as any; const engine = new MockScreenerEngine() as any; const calendar = new CalendarService(new MockYahooClient() as any); const controller = new CallsController(repository, engine, calendar); let callsEndpointRegistered = false; const mockApp = { get: (path: string) => { if (path === '/api/calls') callsEndpointRegistered = true; }, post: () => {}, delete: () => {}, }; controller.register(mockApp as any); assert.ok(callsEndpointRegistered); }); await t.test('registers POST /api/calls endpoint', async () => { const repository = new MockMarketCallRepository() as any; const engine = new MockScreenerEngine() as any; const calendar = new CalendarService(new MockYahooClient() as any); const controller = new CallsController(repository, engine, calendar); let createEndpointRegistered = false; const mockApp = { get: () => {}, post: (path: string) => { if (path === '/api/calls') createEndpointRegistered = true; }, delete: () => {}, }; controller.register(mockApp as any); assert.ok(createEndpointRegistered); }); await t.test('registers DELETE /api/calls/:id endpoint', async () => { const repository = new MockMarketCallRepository() as any; const engine = new MockScreenerEngine() as any; const calendar = new CalendarService(new MockYahooClient() as any); const controller = new CallsController(repository, engine, calendar); let deleteEndpointRegistered = false; const mockApp = { get: () => {}, post: () => {}, delete: (path: string) => { if (path === '/api/calls/:id') deleteEndpointRegistered = true; }, }; controller.register(mockApp as any); assert.ok(deleteEndpointRegistered); }); await t.test('lists all market calls', async () => { const repository = new MockMarketCallRepository() as any; const calls = await repository.list(); assert.ok(Array.isArray(calls)); assert.equal(calls.length, 1); assert.equal(calls[0].title, 'AAPL Post-Earnings'); }); await t.test('returns calls sorted by date (newest first)', async () => { class MultiCallRepository { private calls = [ { id: '1', title: 'Old Call', quarter: 'Q1 2024', thesis: 'Old thesis', tickers: ['AAPL'], date: '2024-01-01', snapshot: {}, }, { id: '2', title: 'New Call', quarter: 'Q2 2024', thesis: 'New thesis', tickers: ['MSFT'], date: '2024-05-01', snapshot: {}, }, ]; async list() { return this.calls.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); } async get(id: string) { return this.calls.find((c) => c.id === id) || null; } async create(call: any) { return call; } async delete(_id: string) { return true; } } const repository = new MultiCallRepository() as any; const calls = await repository.list(); assert.equal(calls[0].title, 'New Call'); assert.equal(calls[1].title, 'Old Call'); }); await t.test('creates new market call', async () => { const repository = new MockMarketCallRepository() as any; const newCall = { title: 'MSFT Q3 2024', quarter: 'Q3 2024', thesis: 'Cloud growth acceleration', tickers: ['MSFT'], date: '2024-07-01', snapshot: {}, } as MarketCall; const created = await repository.create(newCall); assert.ok(created.id); assert.equal(created.title, 'MSFT Q3 2024'); }); await t.test('retrieves single market call by id', async () => { const repository = new MockMarketCallRepository() as any; const call = await repository.get('1'); assert.ok(call); assert.equal(call.id, '1'); assert.equal(call.title, 'AAPL Post-Earnings'); }); await t.test('deletes market call', async () => { const repository = new MockMarketCallRepository() as any; const deleted = await repository.delete('1'); assert.ok(deleted); const call = await repository.get('1'); assert.equal(call, null); }); await t.test('returns 404 for non-existent call', async () => { const repository = new MockMarketCallRepository() as any; const call = await repository.get('999'); assert.equal(call, null); }); await t.test('screens tickers in call', async () => { const repository = new MockMarketCallRepository() as any; const engine = new MockScreenerEngine() as any; const call = await repository.get('1'); if (call) { const results = await engine.screenTickers(call.tickers); assert.ok(results); assert.ok(results.STOCK || results.ERROR); } }); await t.test('handles multiple tickers in call', async () => { const repository = new MockMarketCallRepository() as any; const engine = new MockScreenerEngine() as any; const newCall = { title: 'Tech Quartet', quarter: 'Q3 2024', thesis: 'All tech leaders', tickers: ['AAPL', 'MSFT', 'NVDA', 'GOOG'], date: '2024-07-01', snapshot: {}, } as MarketCall; const created = await repository.create(newCall); const results = await engine.screenTickers(created.tickers); assert.ok(created.tickers.length === 4); assert.ok(results); // Should have screened all 4 tickers }); await t.test('gets calendar events for call tickers', async () => { const repository = new MockMarketCallRepository() as any; const calendar = new CalendarService(new MockYahooClient() as any); const call = await repository.get('1'); if (call) { const result = await calendar.getEvents(call.tickers); assert.ok(Array.isArray(result.events)); assert.ok(Array.isArray(result.tickers)); } }); await t.test('call includes a snapshot of entry prices', async () => { const repository = new MockMarketCallRepository() as any; const call = await repository.get('1'); assert.ok(call); // MarketCall.snapshot is Record, not an array assert.equal(typeof call.snapshot, 'object'); assert.ok(!Array.isArray(call.snapshot)); }); });