Automate issuer overlay creation from ticker searches
This commit is contained in:
146
lib/server/repos/issuer-overlays.test.ts
Normal file
146
lib/server/repos/issuer-overlays.test.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "bun:test";
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { Database } from "bun:sqlite";
|
||||
import { __dbInternals } from "@/lib/server/db";
|
||||
import type {
|
||||
IssuerOverlayDefinition,
|
||||
IssuerOverlayDiagnostics,
|
||||
IssuerOverlayStats,
|
||||
} from "@/lib/server/db/schema";
|
||||
|
||||
let tempDir: string | null = null;
|
||||
let sqliteClient: Database | null = null;
|
||||
let overlayRepo: typeof import("./issuer-overlays") | null = null;
|
||||
|
||||
function resetDbSingletons() {
|
||||
const globalState = globalThis as typeof globalThis & {
|
||||
__fiscalSqliteClient?: Database;
|
||||
__fiscalDrizzleDb?: unknown;
|
||||
__financialIngestionSchemaStatus?: unknown;
|
||||
};
|
||||
|
||||
globalState.__fiscalSqliteClient?.close();
|
||||
globalState.__fiscalSqliteClient = undefined;
|
||||
globalState.__fiscalDrizzleDb = undefined;
|
||||
globalState.__financialIngestionSchemaStatus = undefined;
|
||||
}
|
||||
|
||||
function sampleDefinition(): IssuerOverlayDefinition {
|
||||
return {
|
||||
version: "fiscal-v1",
|
||||
ticker: "AAPL",
|
||||
pack: "core",
|
||||
mappings: [
|
||||
{
|
||||
surface_key: "revenue",
|
||||
statement: "income",
|
||||
allowed_source_concepts: ["aapl:ServicesRevenue", "aapl:ProductRevenue"],
|
||||
allowed_authoritative_concepts: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function sampleDiagnostics(): IssuerOverlayDiagnostics {
|
||||
return {
|
||||
pack: "core",
|
||||
sampledSnapshotIds: [11, 12],
|
||||
acceptedMappings: [
|
||||
{
|
||||
qname: "aapl:ServicesRevenue",
|
||||
surface_key: "revenue",
|
||||
statement: "income",
|
||||
reason: "local_name_match",
|
||||
source_snapshot_ids: [11, 12],
|
||||
},
|
||||
],
|
||||
rejectedMappings: [],
|
||||
};
|
||||
}
|
||||
|
||||
function sampleStats(): IssuerOverlayStats {
|
||||
return {
|
||||
pack: "core",
|
||||
sampledSnapshotCount: 2,
|
||||
sampledSnapshotIds: [11, 12],
|
||||
acceptedMappingCount: 1,
|
||||
rejectedMappingCount: 0,
|
||||
publishedRevisionNumber: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe("issuer overlay repo", () => {
|
||||
beforeAll(async () => {
|
||||
tempDir = mkdtempSync(join(tmpdir(), "fiscal-issuer-overlay-"));
|
||||
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;");
|
||||
__dbInternals.ensureLocalSqliteSchema(sqliteClient);
|
||||
|
||||
const globalState = globalThis as typeof globalThis & {
|
||||
__fiscalSqliteClient?: Database;
|
||||
__fiscalDrizzleDb?: unknown;
|
||||
};
|
||||
globalState.__fiscalSqliteClient = sqliteClient;
|
||||
globalState.__fiscalDrizzleDb = undefined;
|
||||
|
||||
overlayRepo = await import("./issuer-overlays");
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
sqliteClient?.close();
|
||||
resetDbSingletons();
|
||||
if (tempDir) {
|
||||
rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sqliteClient?.exec("DELETE FROM issuer_overlay;");
|
||||
sqliteClient?.exec("DELETE FROM issuer_overlay_revision;");
|
||||
});
|
||||
|
||||
it("creates an empty overlay row on ensure", async () => {
|
||||
if (!overlayRepo) {
|
||||
throw new Error("overlay repo not initialized");
|
||||
}
|
||||
|
||||
const overlay = await overlayRepo.ensureIssuerOverlayRow("aapl");
|
||||
expect(overlay?.ticker).toBe("AAPL");
|
||||
expect(overlay?.status).toBe("empty");
|
||||
expect(overlay?.active_revision).toBeNull();
|
||||
});
|
||||
|
||||
it("publishes and deduplicates overlay revisions by content hash", async () => {
|
||||
if (!overlayRepo) {
|
||||
throw new Error("overlay repo not initialized");
|
||||
}
|
||||
|
||||
const first = await overlayRepo.publishIssuerOverlayRevision({
|
||||
ticker: "AAPL",
|
||||
definition: sampleDefinition(),
|
||||
diagnostics: sampleDiagnostics(),
|
||||
stats: sampleStats(),
|
||||
});
|
||||
const second = await overlayRepo.publishIssuerOverlayRevision({
|
||||
ticker: "AAPL",
|
||||
definition: sampleDefinition(),
|
||||
diagnostics: sampleDiagnostics(),
|
||||
stats: sampleStats(),
|
||||
});
|
||||
const overlay = await overlayRepo.getIssuerOverlay("AAPL");
|
||||
const revisions = await overlayRepo.listIssuerOverlayRevisions("AAPL");
|
||||
|
||||
expect(first.published).toBe(true);
|
||||
expect(second.published).toBe(false);
|
||||
expect(overlay?.active_revision?.id).toBe(first.revision.id);
|
||||
expect(revisions).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user