/**
* Unit tests for search strategy logic
* Tests the fix for vector similarity search behavior
*/
import { describe, it, expect } from 'vitest';
describe('Search Strategy Logic', () => {
describe('Strategy Selection', () => {
it('should identify similarity strategy requires pure vector search', () => {
const strategy = 'similarity';
const usePureVectorSearch = strategy === 'similarity';
expect(usePureVectorSearch).toBe(true);
});
it('should identify composite strategy allows hybrid search', () => {
const strategy = 'composite';
const usePureVectorSearch = strategy === 'similarity';
expect(usePureVectorSearch).toBe(false);
});
it('should identify other strategies allow hybrid search', () => {
const strategies = ['recency', 'frequency', 'importance'];
for (const strategy of strategies) {
const usePureVectorSearch = strategy === 'similarity';
expect(usePureVectorSearch).toBe(false);
}
});
});
describe('Default Threshold Selection', () => {
it('should use 0.3 threshold for similarity strategy', () => {
const usePureVectorSearch = true;
const defaultThreshold = usePureVectorSearch ? 0.3 : 0.6;
expect(defaultThreshold).toBe(0.3);
});
it('should use 0.6 threshold for other strategies', () => {
const usePureVectorSearch = false;
const defaultThreshold = usePureVectorSearch ? 0.3 : 0.6;
expect(defaultThreshold).toBe(0.6);
});
it('should allow custom threshold to override default', () => {
const customThreshold = 0.5;
const usePureVectorSearch = true;
const defaultThreshold = usePureVectorSearch ? 0.3 : 0.6;
const actualThreshold = customThreshold !== undefined ? customThreshold : defaultThreshold;
expect(actualThreshold).toBe(0.5);
});
});
describe('Fallback Behavior', () => {
it('should not allow fallback with similarity strategy', () => {
const usePureVectorSearch = true;
const shouldFallbackToTextSearch = !usePureVectorSearch;
expect(shouldFallbackToTextSearch).toBe(false);
});
it('should allow fallback with other strategies', () => {
const usePureVectorSearch = false;
const shouldFallbackToTextSearch = !usePureVectorSearch;
expect(shouldFallbackToTextSearch).toBe(true);
});
});
describe('Result Sorting', () => {
it('should skip re-sorting for similarity strategy', () => {
const strategy = 'similarity';
const shouldApplyAdditionalSorting = strategy !== 'similarity';
expect(shouldApplyAdditionalSorting).toBe(false);
});
it('should apply sorting for other strategies', () => {
const strategies = ['composite', 'recency', 'frequency', 'importance'];
for (const strategy of strategies) {
const shouldApplyAdditionalSorting = strategy !== 'similarity';
expect(shouldApplyAdditionalSorting).toBe(true);
}
});
});
describe('Error Handling', () => {
it('should fail fast with similarity strategy when embeddings unavailable', () => {
const usePureVectorSearch = true;
const embeddingGenerationFailed = true;
const shouldReturnError = usePureVectorSearch && embeddingGenerationFailed;
expect(shouldReturnError).toBe(true);
});
it('should fallback with other strategies when embeddings unavailable', () => {
const usePureVectorSearch = false;
const embeddingGenerationFailed = true;
const shouldReturnError = usePureVectorSearch && embeddingGenerationFailed;
expect(shouldReturnError).toBe(false);
});
});
describe('Threshold Behavior', () => {
it('should recognize 0.3 allows semantic matching', () => {
// At 0.3 threshold, semantically related terms should match
// Example: "car" and "automobile" typically have 0.4-0.6 cosine similarity
const threshold = 0.3;
const exampleSimilarity = 0.45; // Typical for synonyms
expect(exampleSimilarity).toBeGreaterThanOrEqual(threshold);
});
it('should recognize 0.6 requires closer matching', () => {
// At 0.6 threshold, only closely related terms match
const threshold = 0.6;
const weakSimilarity = 0.45; // Synonyms
const strongSimilarity = 0.75; // Near-duplicates
expect(weakSimilarity).toBeLessThan(threshold);
expect(strongSimilarity).toBeGreaterThanOrEqual(threshold);
});
});
describe('Message Formatting', () => {
it('should indicate pure vector search for similarity strategy', () => {
const usePureVectorSearch = true;
const message = usePureVectorSearch
? 'Found X memories (semantic/vector search only)'
: 'Found X memories (text search)';
expect(message).toContain('semantic/vector search only');
});
it('should indicate hybrid approach for other strategies', () => {
const usePureVectorSearch = false;
const vectorSearchUsed = true;
const textSearchUsed = true;
const message = !usePureVectorSearch && vectorSearchUsed && textSearchUsed
? 'Found X memories (Y via semantic search, Z via text search)'
: 'Found X memories';
expect(message).toContain('via semantic search');
expect(message).toContain('via text search');
});
});
});
describe('Vector Search Implementation Verification', () => {
it('should confirm cosine similarity calculation is used', () => {
// This test verifies that the EmbeddingService.findMostSimilar() method
// uses actual cosine similarity calculation, not keyword matching
// Cosine similarity formula: dot(A, B) / (||A|| * ||B||)
const vectorA = [1, 0, 0];
const vectorB = [0.5, 0.866, 0]; // 60 degrees from A
// Calculate cosine similarity manually
const dotProduct = vectorA[0] * vectorB[0] + vectorA[1] * vectorB[1] + vectorA[2] * vectorB[2];
const normA = Math.sqrt(vectorA[0]**2 + vectorA[1]**2 + vectorA[2]**2);
const normB = Math.sqrt(vectorB[0]**2 + vectorB[1]**2 + vectorB[2]**2);
const cosineSimilarity = dotProduct / (normA * normB);
// At 60 degrees, cosine should be approximately 0.5
expect(cosineSimilarity).toBeCloseTo(0.5, 2);
});
it('should confirm threshold filtering works correctly', () => {
const results = [
{ similarity: 0.8, data: 'high' },
{ similarity: 0.5, data: 'medium' },
{ similarity: 0.2, data: 'low' },
];
const threshold = 0.4;
const filtered = results.filter(r => r.similarity >= threshold);
expect(filtered).toHaveLength(2);
expect(filtered[0].data).toBe('high');
expect(filtered[1].data).toBe('medium');
});
it('should confirm results are sorted by similarity descending', () => {
const results = [
{ similarity: 0.5, data: 'medium' },
{ similarity: 0.8, data: 'high' },
{ similarity: 0.2, data: 'low' },
];
const sorted = [...results].sort((a, b) => b.similarity - a.similarity);
expect(sorted[0].similarity).toBe(0.8);
expect(sorted[1].similarity).toBe(0.5);
expect(sorted[2].similarity).toBe(0.2);
});
});