Files
Neon-Desk/lib/server/repos/company-overview-cache.test.ts

203 lines
5.4 KiB
TypeScript

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');
});
});