test: close unified ingest v1 expectations

This commit is contained in:
Andrey Avtomonov 2026-05-13 18:40:46 +02:00
parent 23dba892cd
commit 1180fffdeb
9 changed files with 48 additions and 20 deletions

View file

@ -1,4 +1,3 @@
import { resolve } from 'node:path';
import type { KtxProgressPort, KtxProgressUpdateOptions } from '@ktx/context/scan';
import type { KtxCliIo } from './index.js';
import type { KtxIngestProgressUpdate } from './ingest.js';

View file

@ -2,7 +2,7 @@ import type { KtxProjectConfig, KtxProjectConnectionConfig } from '@ktx/context/
export type KtxDatabaseContextDepth = 'fast' | 'deep';
export const KTX_DATABASE_DRIVER_IDS = new Set([
const KTX_DATABASE_DRIVER_IDS = new Set([
'sqlite',
'postgres',
'postgresql',
@ -23,7 +23,7 @@ export function isDatabaseDriver(driver: string): boolean {
return KTX_DATABASE_DRIVER_IDS.has(driver.trim().toLowerCase());
}
export function connectionContextRecord(connection: KtxProjectConnectionConfig): Record<string, unknown> {
function connectionContextRecord(connection: KtxProjectConnectionConfig): Record<string, unknown> {
const context = connection.context;
return typeof context === 'object' && context !== null && !Array.isArray(context)
? (context as Record<string, unknown>)

View file

@ -46,7 +46,7 @@ function deepReadyProject(
connections,
llm: {
...config.llm,
provider: { backend: 'gateway', gateway: { api_key: 'env:KTX_GATEWAY_API_KEY' } },
provider: { backend: 'gateway', gateway: { api_key: 'env:KTX_GATEWAY_API_KEY' } }, // pragma: allowlist secret
models: { default: 'gpt-test' },
},
scan: {

View file

@ -109,7 +109,10 @@ const queryHistoryDialectByDriver = new Map<string, HistoricSqlDialect>([
]);
function storedQueryHistory(connection: KtxProjectConnectionConfig): Record<string, unknown> {
const value = connection.context?.queryHistory;
const context = connection.context;
const contextRecord =
context && typeof context === 'object' && !Array.isArray(context) ? (context as Record<string, unknown>) : {};
const value = contextRecord.queryHistory;
return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
}

View file

@ -15,6 +15,7 @@ import {
contextBuildCommands,
readKtxSetupContextState,
runKtxSetupContextStep,
type KtxSetupContextDeps,
writeKtxSetupContextState,
} from './setup-context.js';
@ -39,7 +40,16 @@ function makeIo() {
};
}
async function writeReadyProject(projectDir: string, overrides: Partial<KtxProjectConfig> = {}) {
type ReadyProjectOverrides = Omit<Partial<KtxProjectConfig>, 'ingest' | 'llm' | 'scan'> & {
ingest?: Partial<KtxProjectConfig['ingest']>;
llm?: Partial<KtxProjectConfig['llm']>;
scan?: Omit<Partial<KtxProjectConfig['scan']>, 'enrichment' | 'relationships'> & {
enrichment?: Partial<KtxProjectConfig['scan']['enrichment']>;
relationships?: Partial<KtxProjectConfig['scan']['relationships']>;
};
};
async function writeReadyProject(projectDir: string, overrides: ReadyProjectOverrides = {}) {
const defaults = buildDefaultKtxProjectConfig('revenue');
const readyConfig: KtxProjectConfig = {
...defaults,
@ -333,7 +343,9 @@ describe('setup context build state', () => {
await writeFile(join(tempDir, 'wiki', 'global', 'metrics.md'), '# Metrics\n');
await writeReadyEnrichedScanReport(tempDir);
const io = makeIo();
const runContextBuildMock = vi.fn(async () => ({ exitCode: 0 }));
const runContextBuildMock = vi.fn<NonNullable<KtxSetupContextDeps['runContextBuild']>>(async () => ({
exitCode: 0,
}));
await expect(
runKtxSetupContextStep(
@ -415,7 +427,9 @@ describe('setup context build state', () => {
manifestShards: ['semantic-layer/warehouse/_schema/public.yaml'],
});
const io = makeIo();
const runContextBuildMock = vi.fn(async () => ({ exitCode: 0 }));
const runContextBuildMock = vi.fn<NonNullable<KtxSetupContextDeps['runContextBuild']>>(async () => ({
exitCode: 0,
}));
await expect(
runKtxSetupContextStep(
@ -438,7 +452,9 @@ describe('setup context build state', () => {
scan: { enrichment: { mode: 'none' } },
});
const io = makeIo();
const runContextBuildMock = vi.fn(async () => ({ exitCode: 0 }));
const runContextBuildMock = vi.fn<NonNullable<KtxSetupContextDeps['runContextBuild']>>(async () => ({
exitCode: 0,
}));
const verifyContextReady = vi.fn(async () => ({
ready: true,
agentContextReady: true,
@ -472,7 +488,7 @@ describe('setup context build state', () => {
await writeReadyProject(tempDir, {
connections: { warehouse: { driver: 'postgres', readonly: true } },
llm: {
provider: { backend: 'gateway', gateway: { api_key: 'env:KTX_GATEWAY_API_KEY' } },
provider: { backend: 'gateway', gateway: { api_key: 'env:KTX_GATEWAY_API_KEY' } }, // pragma: allowlist secret
models: { default: 'gpt-test' },
},
scan: {

View file

@ -153,10 +153,18 @@ function normalizeState(projectDir: string, value: unknown): KtxSetupContextStat
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
return notStartedState(projectDir);
}
const record = value as Partial<KtxSetupContextState>;
const rawStatus = record.status ?? 'not_started';
const record = value as Record<string, unknown>;
const rawStatus = typeof record.status === 'string' ? record.status : 'not_started';
const legacyActive = rawStatus === 'detached' || rawStatus === 'paused' || rawStatus === 'running';
const status: KtxSetupContextBuildStatus = legacyActive ? 'stale' : rawStatus;
const status: KtxSetupContextBuildStatus = legacyActive
? 'stale'
: rawStatus === 'completed' ||
rawStatus === 'failed' ||
rawStatus === 'interrupted' ||
rawStatus === 'not_started' ||
rawStatus === 'stale'
? rawStatus
: 'not_started';
const runId = typeof record.runId === 'string' && record.runId.length > 0 ? record.runId : undefined;
return {
...(runId ? { runId } : {}),

View file

@ -1438,8 +1438,14 @@ describe('setup databases step', () => {
},
});
expect(config.connections.warehouse.historicSql).toBeUndefined();
expect(config.connections.warehouse.context?.queryHistory).not.toHaveProperty('windowDays');
expect(config.connections.warehouse.context?.queryHistory).not.toHaveProperty('redactionPatterns');
const warehouseContext =
config.connections.warehouse.context &&
typeof config.connections.warehouse.context === 'object' &&
!Array.isArray(config.connections.warehouse.context)
? (config.connections.warehouse.context as Record<string, unknown>)
: {};
expect(warehouseContext.queryHistory).not.toHaveProperty('windowDays');
expect(warehouseContext.queryHistory).not.toHaveProperty('redactionPatterns');
expect(configText).not.toContain('live-database');
expect(configText).not.toContain('historic-sql');
expect(configText).not.toMatch(/^\s+adapters:/m);

View file

@ -1655,7 +1655,6 @@ describe('setup status', () => {
const status = await readKtxSetupStatus(tempDir);
expect(status.context.status).toBe('stale');
expect(status.context.watchCommand).toBeUndefined();
const state = await readKtxSetupContextState(tempDir);
expect(state.status).toBe('stale');
});

View file

@ -378,9 +378,6 @@ export function formatKtxSetupStatus(status: KtxSetupStatus): string {
status.agents.length > 0 ? ` (${status.agents.map((agent) => `${agent.target}:${agent.scope}`).join(', ')})` : ''
}`,
];
if (!status.context.ready && status.context.watchCommand && status.context.status === 'running') {
lines.push(`Resume: ${status.context.watchCommand}`);
}
if (!status.context.ready && status.context.status === 'failed' && status.context.detail) {
lines.push(`Retry: ${status.context.retryCommand ?? `ktx setup --project-dir ${status.project.path}`}`);
}
@ -412,7 +409,7 @@ function setupContextReady(status: KtxSetupStatus): boolean {
}
function setupContextActive(status: KtxSetupStatus): boolean {
return status.context.status === 'running' || status.context.status === 'detached';
return status.context.status === 'running';
}
function writeContextNotReadyForAgents(projectDir: string, io: KtxCliIo): void {