3ab8542cf4
- Convert Chroma cosine distance to similarity (1 - distance) for correct threshold comparison - Simplify forbidden directory check to match path segments instead of string prefixes - Add debounced settings save via onPersist callback in ChatView to prevent data loss - Fix workflow engine to pass availableTools to LLM when includeToolCalls is enabled - Add targetFolder support to AutoLinker config and update tests - Harden vault file indexing to await background indexing before processing create/modify/rename events
182 lines
5.6 KiB
TypeScript
182 lines
5.6 KiB
TypeScript
// tests/semantic-cache.test.ts
|
|
|
|
import { ChromaClient } from 'chromadb';
|
|
import { CacheConfig } from '../src/types';
|
|
|
|
// Mock ChromaDB module
|
|
jest.mock('chromadb', () => ({
|
|
ChromaClient: jest.fn().mockImplementation(() => {
|
|
return {
|
|
getOrCreateCollection: jest.fn().mockResolvedValue({
|
|
query: jest.fn(),
|
|
add: jest.fn(),
|
|
upsert: jest.fn(),
|
|
reset: jest.fn(),
|
|
}),
|
|
deleteCollection: jest.fn(),
|
|
reset: jest.fn(),
|
|
};
|
|
}),
|
|
IncludeEnum: {
|
|
Documents: 'documents',
|
|
Embeddings: 'embeddings',
|
|
Metadatas: 'metadatas',
|
|
Distances: 'distances',
|
|
},
|
|
}));
|
|
|
|
import { SemanticCacheService } from '../src/semantic-cache';
|
|
|
|
describe('SemanticCacheService', () => {
|
|
const mockOllamaUrl = 'http://localhost:11434';
|
|
const mockCacheConfig: CacheConfig = {
|
|
enabled: true,
|
|
similarityThreshold: 0.85,
|
|
collectionName: 'test_cache',
|
|
embeddingModel: 'nomic-embed-text',
|
|
chromaURL: 'http://localhost:8000',
|
|
};
|
|
|
|
let cacheService: SemanticCacheService;
|
|
let mockChromaClient: any;
|
|
let mockCollection: any;
|
|
|
|
beforeEach(async () => {
|
|
// Reset all mocks
|
|
jest.clearAllMocks();
|
|
|
|
// Create a fresh instance for each test
|
|
cacheService = new SemanticCacheService(mockOllamaUrl, mockCacheConfig);
|
|
await cacheService.initialize();
|
|
global.fetch = jest.fn().mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve({ embedding: [0.1, 0.2, 0.3] }),
|
|
});
|
|
|
|
// Access the internal mocks
|
|
mockChromaClient = (ChromaClient as jest.Mock).mock.results[0].value;
|
|
mockCollection = await mockChromaClient.getOrCreateCollection.mock.results[0].value;
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should initialize with correct configuration', () => {
|
|
expect(mockChromaClient).toBeDefined();
|
|
expect(mockChromaClient.getOrCreateCollection).toHaveBeenCalledWith({
|
|
name: mockCacheConfig.collectionName,
|
|
metadata: { 'hnsw:space': 'cosine' },
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('initialize', () => {
|
|
it('should initialize the cache collection', async () => {
|
|
await cacheService.initialize();
|
|
|
|
expect(mockChromaClient.getOrCreateCollection).toHaveBeenCalledWith({
|
|
name: mockCacheConfig.collectionName,
|
|
metadata: { 'hnsw:space': 'cosine' },
|
|
});
|
|
});
|
|
|
|
it('should not initialize when cache is disabled', async () => {
|
|
jest.clearAllMocks();
|
|
const disabledConfig: CacheConfig = { ...mockCacheConfig, enabled: false };
|
|
const disabledCacheService = new SemanticCacheService(mockOllamaUrl, disabledConfig);
|
|
|
|
await disabledCacheService.initialize();
|
|
|
|
expect(mockChromaClient.getOrCreateCollection).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('getCache', () => {
|
|
it('should return null when cache is disabled', async () => {
|
|
jest.clearAllMocks();
|
|
const disabledConfig: CacheConfig = { ...mockCacheConfig, enabled: false };
|
|
const disabledCacheService = new SemanticCacheService(mockOllamaUrl, disabledConfig);
|
|
|
|
const result = await disabledCacheService.getCache('test query');
|
|
|
|
expect(result).toBeNull();
|
|
expect(mockCollection.query).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return null when no cache hit', async () => {
|
|
mockCollection.query.mockResolvedValue({
|
|
ids: [[]],
|
|
documents: [[]],
|
|
distances: [[]],
|
|
});
|
|
|
|
const result = await cacheService.getCache('test query');
|
|
|
|
expect(result).toBeNull();
|
|
expect(mockCollection.query).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return cached content when hit', async () => {
|
|
const cachedContent = 'cached response';
|
|
mockCollection.query.mockResolvedValue({
|
|
ids: [['test-id']],
|
|
documents: [[cachedContent]],
|
|
distances: [[0.1]], // Similarity 0.9, above threshold
|
|
});
|
|
|
|
const result = await cacheService.getCache('test query');
|
|
|
|
expect(result).toBe(cachedContent);
|
|
expect(mockCollection.query).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return null when cosine distance is too high', async () => {
|
|
mockCollection.query.mockResolvedValue({
|
|
ids: [['test-id']],
|
|
documents: [['unrelated cached response']],
|
|
distances: [[0.9]], // Similarity 0.1, below threshold
|
|
});
|
|
|
|
const result = await cacheService.getCache('test query');
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('setCache', () => {
|
|
it('should not set cache when disabled', async () => {
|
|
jest.clearAllMocks();
|
|
const disabledConfig: CacheConfig = { ...mockCacheConfig, enabled: false };
|
|
const disabledCacheService = new SemanticCacheService(mockOllamaUrl, disabledConfig);
|
|
|
|
await disabledCacheService.setCache('test query', 'test response');
|
|
|
|
expect(mockCollection.upsert).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should add content to cache', async () => {
|
|
await cacheService.setCache('test query', 'test response');
|
|
|
|
expect(mockCollection.upsert).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('clearCache', () => {
|
|
it('should not clear when cache is disabled', async () => {
|
|
jest.clearAllMocks();
|
|
const disabledConfig: CacheConfig = { ...mockCacheConfig, enabled: false };
|
|
const disabledCacheService = new SemanticCacheService(mockOllamaUrl, disabledConfig);
|
|
|
|
await disabledCacheService.clearCache();
|
|
|
|
expect(mockChromaClient.deleteCollection).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should clear the cache via deleteCollection', async () => {
|
|
await cacheService.clearCache();
|
|
|
|
expect(mockChromaClient.deleteCollection).toHaveBeenCalledWith({
|
|
name: mockCacheConfig.collectionName,
|
|
});
|
|
});
|
|
});
|
|
});
|