fix bruno collection
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
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: new Date('2024-05-01'),
|
||||
snapshots: [{ ticker: 'AAPL', price: 180, date: new Date('2024-05-01') }],
|
||||
},
|
||||
];
|
||||
|
||||
async list(): Promise<(MarketCall & { id: string })[]> {
|
||||
return this.calls.sort((a, b) => b.date.getTime() - 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<MarketCall & { id: string }> {
|
||||
const id = String(this.calls.length + 1);
|
||||
const newCall = { id, ...call };
|
||||
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].ticker || calls[0].title, 'AAPL Post-Earnings' || 'AAPL');
|
||||
});
|
||||
|
||||
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: new Date('2024-01-01'),
|
||||
snapshots: [],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'New Call',
|
||||
quarter: 'Q2 2024',
|
||||
thesis: 'New thesis',
|
||||
tickers: ['MSFT'],
|
||||
date: new Date('2024-05-01'),
|
||||
snapshots: [],
|
||||
},
|
||||
];
|
||||
|
||||
async list() {
|
||||
return this.calls.sort((a, b) => b.date.getTime() - 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: MarketCall = {
|
||||
title: 'MSFT Q3 2024',
|
||||
quarter: 'Q3 2024',
|
||||
thesis: 'Cloud growth acceleration',
|
||||
tickers: ['MSFT'],
|
||||
date: new Date('2024-07-01'),
|
||||
snapshots: [],
|
||||
};
|
||||
|
||||
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: MarketCall = {
|
||||
title: 'Tech Quartet',
|
||||
quarter: 'Q3 2024',
|
||||
thesis: 'All tech leaders',
|
||||
tickers: ['AAPL', 'MSFT', 'NVDA', 'GOOG'],
|
||||
date: new Date('2024-07-01'),
|
||||
snapshots: [],
|
||||
};
|
||||
|
||||
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 snapshots of entry prices', async () => {
|
||||
const repository = new MockMarketCallRepository() as any;
|
||||
|
||||
const call = await repository.get('1');
|
||||
assert.ok(call);
|
||||
assert.ok(Array.isArray(call.snapshots));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user