test: improve PR coverage

This commit is contained in:
Andrey Avtomonov 2026-05-21 10:29:20 +02:00
parent e6f0d9cd51
commit b43b12729e
6 changed files with 308 additions and 0 deletions

View file

@ -111,6 +111,26 @@ describe('createLocalBundleIngestRuntime', () => {
);
});
it('warns when embeddings are configured but no embedding provider is supplied', () => {
const logger = { log: vi.fn(), warn: vi.fn(), error: vi.fn() };
project.config.ingest.embeddings = {
backend: 'openai',
model: 'text-embedding-3-small',
dimensions: 1536,
};
createLocalBundleIngestRuntime({
project,
adapters: [new FakeSourceAdapter()],
agentRunner: testAgentRunner(),
logger: logger as never,
});
expect(logger.warn).toHaveBeenCalledWith(
'[local-bundle-runtime] embeddings backend "openai" is configured but no embedding provider was passed; embedding-dependent stages will run against a no-op embedding port.',
);
});
it('builds runner deps with local SQLite stores and context tools enabled', async () => {
const agentRunner = testAgentRunner();

View file

@ -88,6 +88,25 @@ describe('createLocalProjectMemoryIngest', () => {
await rm(tempDir, { recursive: true, force: true });
});
it('warns when embeddings are configured but memory ingest is created without an embedding provider', async () => {
const project = await initKtxProject({ projectDir: tempDir });
project.config.ingest.embeddings = {
backend: 'openai',
model: 'text-embedding-3-small',
dimensions: 1536,
};
const logger = { log: vi.fn(), warn: vi.fn(), error: vi.fn() };
createLocalProjectMemoryIngest(project, {
agentRunner: { runLoop: vi.fn() } as never,
logger: logger as never,
});
expect(logger.warn).toHaveBeenCalledWith(
'[memory-ingest] embeddings backend "openai" is configured but no embedding provider was passed; semantic search will fall back to a no-op embedding port.',
);
});
it('captures a wiki page through the local memory agent and persists pollable status', async () => {
const project = await initKtxProject({ projectDir: tempDir });
const agentRunner = {

View file

@ -176,6 +176,28 @@ llm:
});
});
it('requires a non-empty Vertex location when the Vertex provider block is present', () => {
const yaml = `
llm:
provider:
backend: vertex
vertex:
project: local-gcp-project
`;
expect(() => parseKtxProjectConfig(yaml)).toThrow(/llm\.provider\.vertex\.location/);
const validation = validateKtxProjectConfig(yaml);
expect(validation.ok).toBe(false);
expect(validation.issues).toEqual(
expect.arrayContaining([
expect.objectContaining({
path: 'llm.provider.vertex.location',
}),
]),
);
});
it('parses Claude Code as a first-class LLM backend', () => {
const config = parseKtxProjectConfig(`
llm:

View file

@ -67,6 +67,23 @@ describe('listConnectionIdsWithNames', () => {
});
});
describe('loadSource', () => {
it('warns and returns null when an existing source file has invalid YAML', async () => {
const logger = { log: vi.fn(), warn: vi.fn(), error: vi.fn() };
const configService = {
readFile: vi.fn().mockResolvedValue({ content: 'name: [' }),
};
const service = new SemanticLayerService(configService as never, connectionCatalog(), pythonPort, logger as never);
await expect(service.loadSource('warehouse', 'orders')).resolves.toBeNull();
expect(configService.readFile).toHaveBeenCalledWith('semantic-layer/warehouse/orders.yaml');
expect(logger.warn).toHaveBeenCalledWith(
expect.stringContaining('[loadSource] warehouse/orders.yaml: YAML parse failed:'),
);
});
});
describe('composeOverlay', () => {
it('carries top-level segments from overlay into the composed source', () => {
const overlay = {
@ -856,6 +873,22 @@ describe('loadAllSources — standalone enrichment via inherits_columns_from', (
expect(loadErrors.join('\n')).toContain(overlayPath);
expect(loadErrors.join('\n')).toContain("move it to 'column_overrides:'");
});
it('reports and logs directory listing failures instead of treating them as empty sources', async () => {
const logger = { log: vi.fn(), warn: vi.fn(), error: vi.fn() };
configService.listFiles.mockRejectedValue(new Error('permission denied'));
service = new SemanticLayerService(configService as never, connectionCatalog(), pythonPort, logger as never);
const { sources, loadErrors } = await service.loadAllSources('conn-1');
expect(sources).toEqual([]);
expect(loadErrors).toEqual([
'Failed to list semantic-layer files under semantic-layer/conn-1: permission denied',
]);
expect(logger.warn).toHaveBeenCalledWith(
'Failed to list semantic-layer files under semantic-layer/conn-1: permission denied',
);
});
});
describe('validateWithProposedSource', () => {

View file

@ -50,6 +50,27 @@ function makeService() {
const fm: WikiFrontmatter = { summary: 'sum', usage_mode: 'auto' };
describe('KnowledgeWikiService file reads', () => {
it('warns and returns null when an existing page cannot be parsed', async () => {
const { service, configService, logger } = makeService();
configService.readFile.mockResolvedValue({ content: '---\nsummary: [\n---\nBody' });
await expect(service.readPage('GLOBAL', null, 'revenue')).resolves.toBeNull();
expect(configService.readFile).toHaveBeenCalledWith('wiki/global/revenue.md');
expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('[readPage] wiki/global/revenue.md: parse failed:'));
});
it('warns and returns an empty page list when directory listing fails', async () => {
const { service, configService, logger } = makeService();
configService.listFiles.mockRejectedValue(new Error('filesystem unavailable'));
await expect(service.listPageKeys('GLOBAL', null)).resolves.toEqual([]);
expect(logger.warn).toHaveBeenCalledWith('[listPageKeys] wiki/global: filesystem unavailable');
});
});
describe('KnowledgeWikiService.syncIndex result stats', () => {
it('reports scanned, updated, deleted, and embedding counts', async () => {
const { service, pagesRepository, embeddingService, configService } = makeService();