ktx/packages/cli/src/local-scan-connectors.test.ts
Luca Martial b3dcb577d9 misc
2026-05-10 20:44:07 -07:00

140 lines
4.4 KiB
TypeScript

import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { initKtxProject, loadKtxProject } from '@ktx/context/project';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { createKtxCliScanConnector } from './local-scan-connectors.js';
const bigQueryMock = vi.hoisted(() => ({
constructorInputs: [] as Array<{
connectionId: string;
connection: unknown;
maxBytesBilled?: number | string;
}>,
}));
vi.mock('@ktx/connector-bigquery', () => ({
isKtxBigQueryConnectionConfig: (connection: { driver?: unknown } | undefined) =>
String(connection?.driver ?? '').toLowerCase() === 'bigquery',
KtxBigQueryScanConnector: class {
readonly id: string;
readonly driver = 'bigquery';
constructor(options: { connectionId: string; connection: unknown; maxBytesBilled?: number | string }) {
bigQueryMock.constructorInputs.push(options);
this.id = `bigquery:${options.connectionId}`;
}
},
}));
describe('createKtxCliScanConnector', () => {
let tempDir: string;
beforeEach(async () => {
bigQueryMock.constructorInputs.length = 0;
tempDir = await mkdtemp(join(tmpdir(), 'ktx-cli-scan-connector-'));
});
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
it('creates a native sqlite connector from standalone config', async () => {
await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
await writeFile(
join(tempDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: sqlite',
' path: warehouse.db',
' readonly: true',
'',
].join('\n'),
'utf-8',
);
const project = await loadKtxProject({ projectDir: tempDir });
const connector = await createKtxCliScanConnector(project, 'warehouse');
expect(connector.id).toBe('sqlite:warehouse');
expect(connector.driver).toBe('sqlite');
});
it.each([
['maxBytesBilled', ' maxBytesBilled: 123456789', 123456789],
['max_bytes_billed', ' max_bytes_billed: "987654321"', '987654321'],
])('passes BigQuery %s from standalone config', async (_label, byteCapLine, expectedMaxBytesBilled) => {
await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
await writeFile(
join(tempDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: bigquery',
' dataset_id: analytics',
' readonly: true',
byteCapLine,
'',
].join('\n'),
'utf-8',
);
const project = await loadKtxProject({ projectDir: tempDir });
const connector = await createKtxCliScanConnector(project, 'warehouse');
expect(connector.id).toBe('bigquery:warehouse');
expect(connector.driver).toBe('bigquery');
expect(bigQueryMock.constructorInputs).toEqual([
expect.objectContaining({
connectionId: 'warehouse',
maxBytesBilled: expectedMaxBytesBilled,
}),
]);
});
it('throws for structural daemon-only fallback configs', async () => {
await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
await writeFile(
join(tempDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' driver: duckdb',
' path: warehouse.duckdb',
'',
].join('\n'),
'utf-8',
);
const project = await loadKtxProject({ projectDir: tempDir });
await expect(createKtxCliScanConnector(project, 'warehouse')).rejects.toThrow(
'Connection "warehouse" uses driver "duckdb", which has no native standalone KTX scan connector',
);
});
it('throws a clear error when the connection block has no driver field', async () => {
await initKtxProject({ projectDir: tempDir, projectName: 'warehouse' });
await writeFile(
join(tempDir, 'ktx.yaml'),
[
'project: warehouse',
'connections:',
' warehouse:',
' type: postgres',
' url: postgresql://example/db',
' readonly: true',
'',
].join('\n'),
'utf-8',
);
const project = await loadKtxProject({ projectDir: tempDir });
await expect(createKtxCliScanConnector(project, 'warehouse')).rejects.toThrow(
'Connection "warehouse" has no `driver` field in ktx.yaml',
);
});
});