2026-05-21 02:38:34 +02:00
|
|
|
import { describe, expect, it } from 'vitest';
|
2026-05-21 03:27:33 +02:00
|
|
|
import { buildDefaultKtxProjectConfig, type KtxLocalProject, type KtxProjectConfig } from './context/project/index.js';
|
2026-05-21 02:38:34 +02:00
|
|
|
import { buildProjectStatus } from './status-project.js';
|
|
|
|
|
|
|
|
|
|
function projectWithConfig(config: KtxProjectConfig): KtxLocalProject {
|
|
|
|
|
return {
|
|
|
|
|
projectDir: '/work/proj',
|
|
|
|
|
configPath: '/work/proj/ktx.yaml',
|
|
|
|
|
config,
|
|
|
|
|
coreConfig: {} as KtxLocalProject['coreConfig'],
|
|
|
|
|
git: {} as KtxLocalProject['git'],
|
|
|
|
|
fileStore: {} as KtxLocalProject['fileStore'],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function withEmbeddings(
|
|
|
|
|
config: KtxProjectConfig,
|
|
|
|
|
embeddings: KtxProjectConfig['ingest']['embeddings'],
|
|
|
|
|
): KtxProjectConfig {
|
|
|
|
|
return {
|
|
|
|
|
...config,
|
|
|
|
|
ingest: { ...config.ingest, embeddings },
|
|
|
|
|
scan: { ...config.scan, enrichment: { ...config.scan.enrichment, embeddings } },
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function withClaudeCodeLlm(config: KtxProjectConfig): KtxProjectConfig {
|
|
|
|
|
return {
|
|
|
|
|
...config,
|
|
|
|
|
llm: {
|
|
|
|
|
...config.llm,
|
|
|
|
|
provider: { backend: 'claude-code' },
|
|
|
|
|
models: { ...config.llm.models, default: 'sonnet' },
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function baseProjectConfig(): KtxProjectConfig {
|
|
|
|
|
return withClaudeCodeLlm(buildDefaultKtxProjectConfig());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const stubClaudeCodeAuthProbe = async () => ({ ok: true as const });
|
|
|
|
|
|
|
|
|
|
describe('buildProjectStatus embeddings', () => {
|
|
|
|
|
it('reports sentence-transformers with explicit base_url as ok', async () => {
|
|
|
|
|
const project = projectWithConfig(
|
|
|
|
|
withEmbeddings(baseProjectConfig(), {
|
|
|
|
|
backend: 'sentence-transformers',
|
|
|
|
|
model: 'all-MiniLM-L6-v2',
|
|
|
|
|
dimensions: 384,
|
|
|
|
|
sentenceTransformers: { base_url: 'http://my-st:8080', pathPrefix: '' },
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const status = await buildProjectStatus(project, {
|
|
|
|
|
claudeCodeAuthProbe: stubClaudeCodeAuthProbe,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(status.embeddings).toMatchObject({
|
|
|
|
|
backend: 'sentence-transformers',
|
|
|
|
|
status: 'ok',
|
|
|
|
|
detail: 'service: http://my-st:8080',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reports sentence-transformers with omitted base_url as managed daemon (ok)', async () => {
|
|
|
|
|
const project = projectWithConfig(
|
|
|
|
|
withEmbeddings(baseProjectConfig(), {
|
|
|
|
|
backend: 'sentence-transformers',
|
|
|
|
|
model: 'all-MiniLM-L6-v2',
|
|
|
|
|
dimensions: 384,
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const status = await buildProjectStatus(project, {
|
|
|
|
|
claudeCodeAuthProbe: stubClaudeCodeAuthProbe,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(status.embeddings).toMatchObject({
|
|
|
|
|
backend: 'sentence-transformers',
|
|
|
|
|
status: 'ok',
|
|
|
|
|
detail: 'managed local embeddings daemon',
|
|
|
|
|
});
|
|
|
|
|
expect(status.verdictReason).not.toMatch(/embedding credentials missing/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reports sentence-transformers with empty base_url string as managed daemon (ok)', async () => {
|
|
|
|
|
const project = projectWithConfig(
|
|
|
|
|
withEmbeddings(baseProjectConfig(), {
|
|
|
|
|
backend: 'sentence-transformers',
|
|
|
|
|
model: 'all-MiniLM-L6-v2',
|
|
|
|
|
dimensions: 384,
|
|
|
|
|
sentenceTransformers: { base_url: '', pathPrefix: '' },
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const status = await buildProjectStatus(project, {
|
|
|
|
|
claudeCodeAuthProbe: stubClaudeCodeAuthProbe,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(status.embeddings).toMatchObject({
|
|
|
|
|
backend: 'sentence-transformers',
|
|
|
|
|
status: 'ok',
|
|
|
|
|
detail: 'managed local embeddings daemon',
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('reports openai backend with missing key as warn', async () => {
|
|
|
|
|
const project = projectWithConfig(
|
|
|
|
|
withEmbeddings(baseProjectConfig(), {
|
|
|
|
|
backend: 'openai',
|
|
|
|
|
model: 'text-embedding-3-small',
|
|
|
|
|
dimensions: 1536,
|
|
|
|
|
openai: { api_key: 'env:OPENAI_API_KEY' }, // pragma: allowlist secret
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const status = await buildProjectStatus(project, {
|
|
|
|
|
env: {},
|
|
|
|
|
claudeCodeAuthProbe: stubClaudeCodeAuthProbe,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(status.embeddings.status).toBe('warn');
|
|
|
|
|
expect(status.verdictReason).toMatch(/embedding credentials missing/);
|
|
|
|
|
});
|
|
|
|
|
});
|