Skip to main content
Glama
indexing-mocks.js6.77 kB
/** * Mock helpers for indexing tests */ import { vi } from 'vitest' // Constants matching the actual implementation export const BATCH_SIZE = 32 export const BATCH_DELAY_MS = 100 export const EMBEDDING_DIM = 384 export const MAC_ABSOLUTE_EPOCH = 978307200 /** * Creates an in-memory LanceDB mock with table simulation */ export function createLanceDBMock() { const tables = new Map() const createTableMock = (tableData) => ({ query: () => ({ select: (columns) => ({ toArray: vi.fn().mockResolvedValue(tableData?.records || []) }), limit: (n) => ({ toArray: vi.fn().mockResolvedValue((tableData?.records || []).slice(0, n)) }), toArray: vi.fn().mockResolvedValue(tableData?.records || []) }), search: vi.fn().mockImplementation((vector) => ({ limit: (n) => ({ toArray: vi.fn().mockResolvedValue( (tableData?.records || []) .slice(0, n) .map(r => ({ ...r, _distance: Math.random() * 0.5 })) ) }) })), add: vi.fn().mockImplementation((records) => { if (tableData) { tableData.records.push(...records) } return Promise.resolve() }), delete: vi.fn().mockResolvedValue(undefined) }) return { connect: vi.fn().mockResolvedValue({ tableNames: vi.fn().mockImplementation(() => Promise.resolve([...tables.keys()])), createTable: vi.fn().mockImplementation((name, records) => { tables.set(name, { records: [...records] }) return Promise.resolve(createTableMock(tables.get(name))) }), openTable: vi.fn().mockImplementation((name) => { return Promise.resolve(createTableMock(tables.get(name))) }), dropTable: vi.fn().mockImplementation((name) => { tables.delete(name) return Promise.resolve() }) }), tables, createTableMock } } /** * Creates a mock embedding pipeline that returns 384-dim vectors */ export function createEmbeddingMock() { const mockEmbedder = vi.fn().mockImplementation((texts, options) => { const isArray = Array.isArray(texts) const count = isArray ? texts.length : 1 // Generate deterministic vectors based on text content for consistency const data = new Float32Array(count * EMBEDDING_DIM) for (let i = 0; i < count; i++) { const text = isArray ? texts[i] : texts const seed = hashString(text) for (let j = 0; j < EMBEDDING_DIM; j++) { data[i * EMBEDDING_DIM + j] = (Math.sin(seed + j) + 1) / 2 * 0.2 } } return Promise.resolve({ data }) }) return { pipeline: vi.fn().mockResolvedValue(mockEmbedder), mockEmbedder } } // Simple string hash for deterministic mock vectors function hashString(str) { let hash = 0 for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i) hash = ((hash << 5) - hash) + char hash = hash & hash } return hash } /** * Creates a mock file system for email testing */ export function createEmailFileSystemMock(emails = []) { const emailMap = new Map(emails.map(e => [e.path, e.content])) const metaData = {} return { existsSync: vi.fn().mockImplementation(p => { if (emailMap.has(p)) return true if (p.includes('.apple-tools-mcp')) return true if (p.includes('index-meta.json')) return true return false }), readFileSync: vi.fn().mockImplementation((p, encoding) => { if (emailMap.has(p)) return emailMap.get(p) if (p.includes('index-meta.json')) return JSON.stringify(metaData) throw new Error('ENOENT: no such file or directory') }), writeFileSync: vi.fn().mockImplementation((p, content) => { if (p.includes('index-meta.json')) { Object.assign(metaData, JSON.parse(content)) } }), mkdirSync: vi.fn(), unlinkSync: vi.fn(), statSync: vi.fn().mockReturnValue({ isFile: () => true, isDirectory: () => false, mtime: new Date() }), readdirSync: vi.fn().mockReturnValue([]), // Expose internal state for assertions _emailMap: emailMap, _metaData: metaData } } /** * Creates a mock for shell commands (exec, sqlite3, mdfind) */ export function createShellMock(options = {}) { const { mdfindResults = '', findResults = '', messages = [], events = [] } = options return { execAsync: vi.fn().mockImplementation((cmd) => { if (cmd.includes('mdfind')) { return Promise.resolve({ stdout: mdfindResults, stderr: '' }) } if (cmd.includes('find')) { return Promise.resolve({ stdout: findResults, stderr: '' }) } return Promise.resolve({ stdout: '', stderr: '' }) }), safeSqlite3Json: vi.fn().mockImplementation((db, query) => { if (db.includes('chat.db')) { return messages } if (db.includes('Calendar')) { return events } return [] }), safeOsascript: vi.fn().mockReturnValue('') } } /** * Creates a mock for the index metadata file operations */ export function createIndexMetaMock(initialMeta = {}) { let meta = { ...initialMeta } return { load: vi.fn().mockImplementation(() => ({ ...meta })), save: vi.fn().mockImplementation((newMeta) => { meta = { ...meta, ...newMeta } }), getMeta: () => meta, reset: () => { meta = { ...initialMeta } } } } /** * Creates a mock lock file manager */ export function createLockFileMock() { let lockHolder = null let lockContent = null return { acquire: vi.fn().mockImplementation((pid = process.pid) => { if (lockHolder === null || lockHolder === pid) { lockHolder = pid lockContent = String(pid) return true } return false }), release: vi.fn().mockImplementation((pid = process.pid) => { if (lockHolder === pid) { lockHolder = null lockContent = null return true } return false }), isLocked: () => lockHolder !== null, getHolder: () => lockHolder, reset: () => { lockHolder = null lockContent = null } } } /** * Creates a comprehensive mock setup for indexer tests */ export function createIndexerMocks(options = {}) { const lancedb = createLanceDBMock() const embedding = createEmbeddingMock() const fs = createEmailFileSystemMock(options.emails || []) const shell = createShellMock(options) const meta = createIndexMetaMock(options.initialMeta || {}) const lock = createLockFileMock() return { lancedb, embedding, fs, shell, meta, lock, // Convenience method to reset all mocks resetAll: () => { vi.clearAllMocks() lancedb.tables.clear() meta.reset() lock.reset() } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sfls1397/Apple-Tools-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server