Files
market_screener/tests/analyze.test.ts
T

91 lines
3.1 KiB
TypeScript

import test, { mock } from 'node:test';
import assert from 'node:assert/strict';
import { AnthropicClient } from '../server/domains/shared/adapters/AnthropicClient.js';
import { buildApp } from '../server/app.js';
import { MockDatabaseConnection } from './helpers/mockDb.js';
const MOCK_LLM_RESPONSE = JSON.stringify({
summary: 'Mocked analysis for test.',
sentiment: 'NEUTRAL',
affectedIndustries: [],
relatedTickers: [],
});
const mockDb = new MockDatabaseConnection() as never;
test('POST /api/analyze', async (t) => {
// Spy on AnthropicClient.prototype.complete before buildApp wires it up.
// This prevents any real API calls during tests.
const completeSpy = mock.method(
AnthropicClient.prototype,
'complete',
async () => MOCK_LLM_RESPONSE,
);
// Also stub isAvailable so the controller doesn't reject with 400
mock.method(AnthropicClient.prototype, 'isAvailable', () => true, { getter: true });
await t.test('returns analysis when stories match tickers', async () => {
const app = await buildApp({ logger: false, db: mockDb });
const response = await app.inject({
method: 'POST',
url: '/api/analyze',
payload: { tickers: ['AAPL'] },
});
// May return no_stories if catalyst cache is empty in test env — that's fine
assert.ok(
response.statusCode === 200,
`Expected 200, got ${response.statusCode}: ${response.body}`,
);
const body = JSON.parse(response.body);
assert.ok('analysis' in body, 'Response should have analysis field');
});
await t.test('returns 400 when ANTHROPIC_API_KEY is missing and no mock', async () => {
// Reset the isAvailable mock to simulate no API key
mock.method(AnthropicClient.prototype, 'isAvailable', () => false, { getter: true });
const app = await buildApp({ logger: false, db: mockDb });
const response = await app.inject({
method: 'POST',
url: '/api/analyze',
payload: { tickers: ['AAPL'] },
});
assert.equal(response.statusCode, 400);
const body = JSON.parse(response.body);
assert.ok(
body.error?.includes('ANTHROPIC_API_KEY'),
`Expected API key error, got: ${body.error}`,
);
});
await t.test('does not call real Anthropic API', async () => {
// Restore isAvailable to available
mock.method(AnthropicClient.prototype, 'isAvailable', () => true, { getter: true });
const callsBefore = completeSpy.mock.calls.length;
const app = await buildApp({ logger: false, db: mockDb });
await app.inject({
method: 'POST',
url: '/api/analyze',
payload: { tickers: ['NVDA'] },
});
// If complete was called, it used our mock — not the real API
const callsAfter = completeSpy.mock.calls.length;
if (callsAfter > callsBefore) {
// Verify it returned our mock response, not a real API response
const lastCall = completeSpy.mock.calls[completeSpy.mock.calls.length - 1];
assert.ok(lastCall, 'complete() was called with our spy in place');
}
// Either way, no real API call was made (spy intercepts)
});
mock.restoreAll();
});