Add company overview skeleton and cache
This commit is contained in:
202
lib/server/repos/company-overview-cache.test.ts
Normal file
202
lib/server/repos/company-overview-cache.test.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
afterAll,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it
|
||||
} from 'bun:test';
|
||||
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { Database } from 'bun:sqlite';
|
||||
import type { CompanyAnalysis } from '@/lib/types';
|
||||
|
||||
const TEST_USER_ID = 'overview-cache-user';
|
||||
|
||||
let tempDir: string | null = null;
|
||||
let sqliteClient: Database | null = null;
|
||||
let overviewCacheRepo: typeof import('./company-overview-cache') | null = null;
|
||||
|
||||
function resetDbSingletons() {
|
||||
const globalState = globalThis as typeof globalThis & {
|
||||
__fiscalSqliteClient?: Database;
|
||||
__fiscalDrizzleDb?: unknown;
|
||||
};
|
||||
|
||||
globalState.__fiscalSqliteClient?.close();
|
||||
globalState.__fiscalSqliteClient = undefined;
|
||||
globalState.__fiscalDrizzleDb = undefined;
|
||||
}
|
||||
|
||||
function applyMigration(client: Database, fileName: string) {
|
||||
const sql = readFileSync(join(process.cwd(), 'drizzle', fileName), 'utf8');
|
||||
client.exec(sql);
|
||||
}
|
||||
|
||||
function ensureUser(client: Database) {
|
||||
const now = Date.now();
|
||||
client.exec(`
|
||||
INSERT OR REPLACE INTO user (id, name, email, emailVerified, image, createdAt, updatedAt, role, banned, banReason, banExpires)
|
||||
VALUES ('${TEST_USER_ID}', 'Overview Cache User', 'overview-cache@example.com', 1, NULL, ${now}, ${now}, NULL, 0, NULL, NULL);
|
||||
`);
|
||||
}
|
||||
|
||||
function clearCache(client: Database) {
|
||||
client.exec('DELETE FROM company_overview_cache;');
|
||||
}
|
||||
|
||||
function buildAnalysisPayload(companyName: string): CompanyAnalysis {
|
||||
return {
|
||||
company: {
|
||||
ticker: 'MSFT',
|
||||
companyName,
|
||||
sector: null,
|
||||
category: null,
|
||||
tags: [],
|
||||
cik: null
|
||||
},
|
||||
quote: 100,
|
||||
position: null,
|
||||
priceHistory: [],
|
||||
benchmarkHistory: [],
|
||||
financials: [],
|
||||
filings: [],
|
||||
aiReports: [],
|
||||
coverage: null,
|
||||
journalPreview: [],
|
||||
recentAiReports: [],
|
||||
latestFilingSummary: null,
|
||||
keyMetrics: {
|
||||
referenceDate: null,
|
||||
revenue: null,
|
||||
netIncome: null,
|
||||
totalAssets: null,
|
||||
cash: null,
|
||||
debt: null,
|
||||
netMargin: null
|
||||
},
|
||||
companyProfile: {
|
||||
description: null,
|
||||
exchange: null,
|
||||
industry: null,
|
||||
country: null,
|
||||
website: null,
|
||||
fiscalYearEnd: null,
|
||||
employeeCount: null,
|
||||
source: 'unavailable'
|
||||
},
|
||||
valuationSnapshot: {
|
||||
sharesOutstanding: null,
|
||||
marketCap: null,
|
||||
enterpriseValue: null,
|
||||
trailingPe: null,
|
||||
evToRevenue: null,
|
||||
evToEbitda: null,
|
||||
source: 'unavailable'
|
||||
},
|
||||
bullBear: {
|
||||
source: 'unavailable',
|
||||
bull: [],
|
||||
bear: [],
|
||||
updatedAt: null
|
||||
},
|
||||
recentDevelopments: {
|
||||
status: 'unavailable',
|
||||
items: [],
|
||||
weeklySnapshot: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('company overview cache repo', () => {
|
||||
beforeAll(async () => {
|
||||
tempDir = mkdtempSync(join(tmpdir(), 'fiscal-overview-cache-'));
|
||||
const env = process.env as Record<string, string | undefined>;
|
||||
env.DATABASE_URL = `file:${join(tempDir, 'repo.sqlite')}`;
|
||||
env.NODE_ENV = 'test';
|
||||
|
||||
resetDbSingletons();
|
||||
|
||||
sqliteClient = new Database(join(tempDir, 'repo.sqlite'), { create: true });
|
||||
sqliteClient.exec('PRAGMA foreign_keys = ON;');
|
||||
applyMigration(sqliteClient, '0000_cold_silver_centurion.sql');
|
||||
applyMigration(sqliteClient, '0012_company_overview_cache.sql');
|
||||
ensureUser(sqliteClient);
|
||||
|
||||
const globalState = globalThis as typeof globalThis & {
|
||||
__fiscalSqliteClient?: Database;
|
||||
__fiscalDrizzleDb?: unknown;
|
||||
};
|
||||
globalState.__fiscalSqliteClient = sqliteClient;
|
||||
globalState.__fiscalDrizzleDb = undefined;
|
||||
|
||||
overviewCacheRepo = await import('./company-overview-cache');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
sqliteClient?.close();
|
||||
resetDbSingletons();
|
||||
if (tempDir) {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
if (!sqliteClient) {
|
||||
throw new Error('sqlite client not initialized');
|
||||
}
|
||||
|
||||
clearCache(sqliteClient);
|
||||
});
|
||||
|
||||
it('upserts and reloads cached overview payloads', async () => {
|
||||
if (!overviewCacheRepo) {
|
||||
throw new Error('overview cache repo not initialized');
|
||||
}
|
||||
|
||||
const first = await overviewCacheRepo.upsertCompanyOverviewCache({
|
||||
userId: TEST_USER_ID,
|
||||
ticker: 'msft',
|
||||
sourceSignature: 'sig-1',
|
||||
payload: buildAnalysisPayload('Microsoft Corporation')
|
||||
});
|
||||
|
||||
const loaded = await overviewCacheRepo.getCompanyOverviewCache({
|
||||
userId: TEST_USER_ID,
|
||||
ticker: 'MSFT'
|
||||
});
|
||||
|
||||
expect(first.ticker).toBe('MSFT');
|
||||
expect(loaded?.source_signature).toBe('sig-1');
|
||||
expect(loaded?.payload.company.companyName).toBe('Microsoft Corporation');
|
||||
});
|
||||
|
||||
it('updates existing cached rows in place', async () => {
|
||||
if (!overviewCacheRepo) {
|
||||
throw new Error('overview cache repo not initialized');
|
||||
}
|
||||
|
||||
const first = await overviewCacheRepo.upsertCompanyOverviewCache({
|
||||
userId: TEST_USER_ID,
|
||||
ticker: 'MSFT',
|
||||
sourceSignature: 'sig-1',
|
||||
payload: buildAnalysisPayload('Old Name')
|
||||
});
|
||||
const second = await overviewCacheRepo.upsertCompanyOverviewCache({
|
||||
userId: TEST_USER_ID,
|
||||
ticker: 'MSFT',
|
||||
sourceSignature: 'sig-2',
|
||||
payload: buildAnalysisPayload('New Name')
|
||||
});
|
||||
|
||||
const loaded = await overviewCacheRepo.getCompanyOverviewCache({
|
||||
userId: TEST_USER_ID,
|
||||
ticker: 'MSFT'
|
||||
});
|
||||
|
||||
expect(second.id).toBe(first.id);
|
||||
expect(loaded?.source_signature).toBe('sig-2');
|
||||
expect(loaded?.payload.company.companyName).toBe('New Name');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user