phase-10.5: test case fixes and updated postman collection

This commit is contained in:
saikiranvella
2026-06-12 00:47:41 -04:00
parent 65907a9b8d
commit 6cb4c93a0e
8 changed files with 524 additions and 668 deletions
+20 -10
View File
@@ -40,6 +40,8 @@ import {
interface BuildAppOptions {
logger?: boolean;
db?: DatabaseConnection;
/** Inject a stub in tests to avoid live Yahoo news fetches. */
catalystCache?: CatalystCache;
}
// ── JWT auth helpers ─────────────────────────────────────────────────────────
@@ -78,7 +80,11 @@ function makeRoleGuard(required: 'trader' | 'admin') {
// 3. Create barrel: server/domains/<domain>/index.ts
// 4. Import from domain and register controller below
// ───────────────────────────────────────────────────────────────────────────
export async function buildApp({ logger = true, db: injectedDb }: BuildAppOptions = {}) {
export async function buildApp({
logger = true,
db: injectedDb,
catalystCache: injectedCache,
}: BuildAppOptions = {}) {
const app = Fastify({ logger });
await app.register(cors, {
@@ -126,7 +132,7 @@ export async function buildApp({ logger = true, db: injectedDb }: BuildAppOption
const advisor = new PortfolioAdvisor(yahoo);
const calSvc = new CalendarService(yahoo);
const llm = new LLMAnalyst({ logger: noopLogger });
const catalystCache = new CatalystCache({ logger: noopLogger }); // Singleton, cached for 15m
const catalystCache = injectedCache ?? new CatalystCache({ logger: noopLogger }); // Singleton, 15m cache
// Auth domain — generate a fresh invite code on every boot and print it
const INVITE_CODE = randomBytes(12).toString('hex'); // 24-char hex string
@@ -136,14 +142,18 @@ export async function buildApp({ logger = true, db: injectedDb }: BuildAppOption
const innerWidth = Math.max(line1.length, line2.length) + 2;
const hr = '─'.repeat(innerWidth);
const pad = (s: string) => `${s}${' '.repeat(innerWidth - 1 - s.length)}`;
/* eslint-disable no-console -- boot-time invite code must reach the operator's terminal */
console.log(`\n┌${hr}`);
console.log(pad(''));
console.log(pad(line1));
console.log(pad(line2));
console.log(pad(''));
console.log(`${hr}\n`);
/* eslint-enable no-console */
// Never print the invite code when the logger is disabled (tests) — secrets
// don't belong in test output.
if (logger !== false) {
/* eslint-disable no-console -- boot-time invite code must reach the operator's terminal */
console.log(`\n┌${hr}`);
console.log(pad(''));
console.log(pad(line1));
console.log(pad(line2));
console.log(pad(''));
console.log(`${hr}\n`);
/* eslint-enable no-console */
}
const userStore = new UserStore(db);
const authService = new AuthService(userStore, JWT_SECRET);
+10 -6
View File
@@ -35,12 +35,14 @@ export class ScreenerEngine {
private static readonly BATCH_DELAY_MS = 1000;
private logger: Logger;
private readonly batchDelayMs: number;
constructor(
private readonly client: YahooFinanceClient,
private readonly benchmarkProvider: BenchmarkProvider,
{ logger }: ScreenerEngineOptions = {},
{ logger, batchDelayMs }: ScreenerEngineOptions = {},
) {
this.batchDelayMs = batchDelayMs ?? ScreenerEngine.BATCH_DELAY_MS;
// eslint-disable-next-line no-console
this.logger = logger ?? {
write: (msg: string) => process.stdout.write(msg),
@@ -65,11 +67,12 @@ export class ScreenerEngine {
const chunks = chunkArray(tickers, ScreenerEngine.BATCH_SIZE);
let processed = 0;
for (const chunk of chunks) {
await this.processBatch(chunk, marketContext, results);
processed += chunk.length;
for (let i = 0; i < chunks.length; i++) {
await this.processBatch(chunks[i], marketContext, results);
processed += chunks[i].length;
this.logProgress(showProgress, processed, tickers.length);
await this.rateLimitDelay();
// Rate-limit pause between batches — never after the last one
if (i < chunks.length - 1) await this.rateLimitDelay();
}
if (showProgress) {
@@ -110,7 +113,8 @@ export class ScreenerEngine {
}
private async rateLimitDelay(): Promise<void> {
await new Promise<void>((r) => setTimeout(r, ScreenerEngine.BATCH_DELAY_MS));
if (this.batchDelayMs <= 0) return;
await new Promise<void>((r) => setTimeout(r, this.batchDelayMs));
}
private async fetch(ticker: string): Promise<MappedData | ErrorResult> {
@@ -90,4 +90,6 @@ export interface RuleSet {
// ── ScreenerEngine ────────────────────────────────────────────────────────
export interface ScreenerEngineOptions {
logger?: Logger;
/** Delay between Yahoo batches (ms). Default 1000; set 0 in tests. */
batchDelayMs?: number;
}