Files
claude-mem/tests/happy-paths/search.test.ts
T
Alex Newman ded9671a82 Refactor worker port handling and improve logging
- Replaced hardcoded migration port with dynamic port retrieval using `getWorkerPort()` in worker-cli.ts.
- Updated context generator to clarify error handling comments.
- Introduced timeout constants in ProcessManager for better maintainability.
- Configured SQLite settings using constants for mmap size and cache size in DatabaseManager.
- Added timeout constants for Git and NPM commands in BranchManager.
- Enhanced error logging in FormattingService and SearchManager to provide more context on failures.
- Removed deprecated silentDebug function and replaced its usage with logger.debug.
- Updated tests to use dynamic worker port retrieval instead of hardcoded values.
2025-12-11 14:49:47 -05:00

330 lines
9.4 KiB
TypeScript

/**
* Happy Path Test: Search (MCP Tools)
*
* Tests that the search functionality correctly finds and returns
* stored observations matching user queries.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { sampleObservation, featureObservation } from '../helpers/scenarios.js';
import { getWorkerPort } from '../../src/shared/worker-utils.js';
describe('Search (MCP Tools)', () => {
const WORKER_PORT = getWorkerPort();
beforeEach(() => {
vi.clearAllMocks();
});
it('finds observations matching query', async () => {
// This tests the happy path:
// User asks "what did we do?" → Search skill queries worker →
// Worker searches database → Returns relevant observations
// Setup: Mock search response with matching observations
const searchResults = [
{
id: 1,
title: 'Parser bugfix',
content: 'Fixed XML parsing issue with self-closing tags',
type: 'bugfix',
created_at: '2024-01-01T10:00:00Z'
},
{
id: 2,
title: 'Parser optimization',
content: 'Improved parser performance by 50%',
type: 'feature',
created_at: '2024-01-02T10:00:00Z'
}
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ results: searchResults, total: 2 })
});
// Execute: Search for "parser"
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search?query=parser&project=claude-mem`
);
// Verify: Found matching observations
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results).toHaveLength(2);
expect(data.results[0].title).toContain('Parser');
expect(data.results[1].title).toContain('Parser');
});
it('returns empty results when no matches found', async () => {
// Setup: Mock empty search results
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ results: [], total: 0 })
});
// Execute: Search for non-existent term
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search?query=nonexistent&project=claude-mem`
);
// Verify: Returns empty results gracefully
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results).toHaveLength(0);
expect(data.total).toBe(0);
});
it('supports filtering by observation type', async () => {
// Setup: Mock filtered search results
const bugfixResults = [
{
id: 1,
title: 'Fixed parser bug',
type: 'bugfix',
created_at: '2024-01-01T10:00:00Z'
}
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ results: bugfixResults, total: 1 })
});
// Execute: Search for bugfixes only
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search/by-type?type=bugfix&project=claude-mem`
);
// Verify: Returns only bugfixes
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results).toHaveLength(1);
expect(data.results[0].type).toBe('bugfix');
});
it('supports filtering by concept tags', async () => {
// Setup: Mock concept-filtered results
const conceptResults = [
{
id: 1,
title: 'How parser works',
concepts: ['how-it-works', 'parser'],
created_at: '2024-01-01T10:00:00Z'
}
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ results: conceptResults, total: 1 })
});
// Execute: Search by concept
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search/by-concept?concept=how-it-works&project=claude-mem`
);
// Verify: Returns observations with that concept
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results).toHaveLength(1);
expect(data.results[0].concepts).toContain('how-it-works');
});
it('supports pagination for large result sets', async () => {
// Setup: Mock paginated results
const page1Results = Array.from({ length: 20 }, (_, i) => ({
id: i + 1,
title: `Observation ${i + 1}`,
created_at: '2024-01-01T10:00:00Z'
}));
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({
results: page1Results,
total: 50,
page: 1,
limit: 20
})
});
// Execute: Search with pagination
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search?query=observation&project=claude-mem&limit=20&offset=0`
);
// Verify: Returns paginated results
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results).toHaveLength(20);
expect(data.total).toBe(50);
expect(data.page).toBe(1);
});
it('supports date range filtering', async () => {
// Setup: Mock date-filtered results
const recentResults = [
{
id: 5,
title: 'Recent observation',
created_at: '2024-01-05T10:00:00Z'
}
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ results: recentResults, total: 1 })
});
// Execute: Search with date range
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search?query=observation&project=claude-mem&dateStart=2024-01-05&dateEnd=2024-01-06`
);
// Verify: Returns observations in date range
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results).toHaveLength(1);
expect(data.results[0].created_at).toContain('2024-01-05');
});
it('returns observations with file references', async () => {
// Setup: Mock results with file paths
const fileResults = [
{
id: 1,
title: 'Updated parser',
files: ['src/parser.ts', 'tests/parser.test.ts'],
created_at: '2024-01-01T10:00:00Z'
}
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ results: fileResults, total: 1 })
});
// Execute: Search
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search?query=parser&project=claude-mem`
);
// Verify: File references included
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results[0].files).toHaveLength(2);
expect(data.results[0].files).toContain('src/parser.ts');
});
it('supports semantic search ranking', async () => {
// Setup: Mock results ordered by relevance
const rankedResults = [
{
id: 2,
title: 'Parser bug fix',
content: 'Fixed critical parser bug',
relevance: 0.95
},
{
id: 5,
title: 'Parser documentation',
content: 'Updated parser docs',
relevance: 0.72
},
{
id: 10,
title: 'Mentioned parser briefly',
content: 'Also updated the parser',
relevance: 0.45
}
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({
results: rankedResults,
total: 3,
orderBy: 'relevance'
})
});
// Execute: Search with relevance ordering
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search?query=parser+bug&project=claude-mem&orderBy=relevance`
);
// Verify: Results ordered by relevance
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results).toHaveLength(3);
expect(data.results[0].relevance).toBeGreaterThan(data.results[1].relevance);
expect(data.results[1].relevance).toBeGreaterThan(data.results[2].relevance);
});
it('handles special characters in search queries', async () => {
// Setup: Mock results for special character query
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ results: [], total: 0 })
});
// Execute: Search with special characters
const queries = [
'function*',
'variable: string',
'array[0]',
'path/to/file',
'tag<content>',
'price $99'
];
for (const query of queries) {
await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search?query=${encodeURIComponent(query)}&project=claude-mem`
);
}
// Verify: All queries processed without error
expect(global.fetch).toHaveBeenCalledTimes(queries.length);
});
it('supports project-specific search', async () => {
// Setup: Mock results from specific project
const projectResults = [
{
id: 1,
title: 'Claude-mem feature',
project: 'claude-mem',
created_at: '2024-01-01T10:00:00Z'
}
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
status: 200,
json: async () => ({ results: projectResults, total: 1 })
});
// Execute: Search specific project
const response = await fetch(
`http://127.0.0.1:${WORKER_PORT}/api/search?query=feature&project=claude-mem`
);
// Verify: Returns only results from that project
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.results).toHaveLength(1);
expect(data.results[0].project).toBe('claude-mem');
});
});