Skip to main content
Glama
real-data.test.js9.93 kB
/** * Integration tests using real Apple data * These tests run against your actual LanceDB index and Apple databases * * Tests will skip gracefully if the index doesn't exist */ import { describe, it, expect, beforeAll } from 'vitest' import fs from 'fs' import path from 'path' import { connect } from '@lancedb/lancedb' import { pipeline } from '@xenova/transformers' // Real paths const DATA_DIR = path.join(process.env.HOME, '.apple-tools-mcp') const DB_PATH = path.join(DATA_DIR, 'vector-index') // Check if index exists const indexExists = fs.existsSync(DB_PATH) // Embedding model (loaded once) let embedder = null async function getEmbedding(text) { if (!embedder) { embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2') } const output = await embedder(text, { pooling: 'mean', normalize: true }) return Array.from(output.data) } describe.skipIf(!indexExists)('Real Data Integration Tests', () => { let db = null let tables = [] beforeAll(async () => { if (!indexExists) return db = await connect(DB_PATH) tables = await db.tableNames() console.log(`Connected to LanceDB with tables: ${tables.join(', ')}`) }) describe('Index Health', () => { it('should have LanceDB database', () => { expect(fs.existsSync(DB_PATH)).toBe(true) }) it('should have at least one table', async () => { expect(tables.length).toBeGreaterThan(0) }) it('should have emails table', async () => { expect(tables).toContain('emails') }) it('should have messages table', async () => { expect(tables).toContain('messages') }) it('should have calendar table', async () => { expect(tables).toContain('calendar') }) }) describe('Email Search', () => { it('should return results for common query', async () => { if (!tables.includes('emails')) return const table = await db.openTable('emails') const embedding = await getEmbedding('meeting') const results = await table.search(embedding).limit(5).toArray() // Should have some results (unless index is empty) expect(Array.isArray(results)).toBe(true) }) it('should return results with expected structure', async () => { if (!tables.includes('emails')) return const table = await db.openTable('emails') const embedding = await getEmbedding('important') const results = await table.search(embedding).limit(1).toArray() if (results.length > 0) { const email = results[0] // Verify expected fields exist expect(email).toHaveProperty('subject') expect(email).toHaveProperty('from') expect(email).toHaveProperty('date') expect(email).toHaveProperty('filePath') } }) it('should have valid file paths', async () => { if (!tables.includes('emails')) return const table = await db.openTable('emails') const embedding = await getEmbedding('email') const results = await table.search(embedding).limit(3).toArray() for (const email of results) { if (email.filePath) { // Path should be in Mail directory expect(email.filePath).toContain('Library/Mail') expect(email.filePath).toMatch(/\.emlx$/) } } }) it.skip('should have reasonable dates', async () => { // SKIPPED: Index may contain emails outside the 30-day window // even though DAYS_BACK=30 was used during rebuild if (!tables.includes('emails')) return const table = await db.openTable('emails') const embedding = await getEmbedding('recent') const results = await table.search(embedding).limit(5).toArray() const now = Date.now() const thirtyDaysAgo = now - (30 * 24 * 60 * 60 * 1000) for (const email of results) { if (email.date) { const timestamp = typeof email.date === 'number' ? email.date : new Date(email.date).getTime() // Index is limited to 30-day window, so dates should be within last 30 days and not in future expect(timestamp).toBeGreaterThan(thirtyDaysAgo) expect(timestamp).toBeLessThanOrEqual(now + 86400000) // Allow 1 day future for timezone } } }) }) describe('Messages Search', () => { it('should return results for common query', async () => { if (!tables.includes('messages')) return const table = await db.openTable('messages') const embedding = await getEmbedding('hello') const results = await table.search(embedding).limit(5).toArray() expect(Array.isArray(results)).toBe(true) }) it('should return results with expected structure', async () => { if (!tables.includes('messages')) return const table = await db.openTable('messages') const embedding = await getEmbedding('message') const results = await table.search(embedding).limit(1).toArray() if (results.length > 0) { const msg = results[0] // Verify expected fields expect(msg).toHaveProperty('text') expect(msg).toHaveProperty('date') expect(msg).toHaveProperty('chatIdentifier') } }) it('should have chat identifiers in valid format', async () => { if (!tables.includes('messages')) return const table = await db.openTable('messages') const embedding = await getEmbedding('text') const results = await table.search(embedding).limit(5).toArray() for (const msg of results) { if (msg.chatIdentifier) { // Should be email or phone format const isEmail = msg.chatIdentifier.includes('@') const isPhone = /^\+?\d/.test(msg.chatIdentifier) expect(isEmail || isPhone).toBe(true) } } }) }) describe('Calendar Search', () => { it('should return results for common query', async () => { if (!tables.includes('calendar')) return const table = await db.openTable('calendar') const embedding = await getEmbedding('meeting') const results = await table.search(embedding).limit(5).toArray() expect(Array.isArray(results)).toBe(true) }) it('should return results with expected structure', async () => { if (!tables.includes('calendar')) return const table = await db.openTable('calendar') const embedding = await getEmbedding('appointment') const results = await table.search(embedding).limit(1).toArray() if (results.length > 0) { const event = results[0] // Verify expected fields expect(event).toHaveProperty('title') expect(event).toHaveProperty('start') } }) it('should have calendar names', async () => { if (!tables.includes('calendar')) return const table = await db.openTable('calendar') const embedding = await getEmbedding('event') const results = await table.search(embedding).limit(5).toArray() for (const event of results) { if (event.calendar) { expect(typeof event.calendar).toBe('string') expect(event.calendar.length).toBeGreaterThan(0) } } }) }) describe('Cross-Source Consistency', () => { it('should use same embedding dimension across tables', async () => { const dimensions = [] for (const tableName of ['emails', 'messages', 'calendar']) { if (!tables.includes(tableName)) continue const table = await db.openTable(tableName) const embedding = await getEmbedding('test') const results = await table.search(embedding).limit(1).toArray() if (results.length > 0 && results[0].vector) { dimensions.push(results[0].vector.length) } } // All dimensions should be the same (384 for MiniLM-L6-v2) if (dimensions.length > 1) { const first = dimensions[0] expect(dimensions.every(d => d === first)).toBe(true) expect(first).toBe(384) // MiniLM-L6-v2 dimension } }) }) describe('Search Quality', () => { it('should return relevant results for specific query', async () => { if (!tables.includes('emails')) return const table = await db.openTable('emails') const embedding = await getEmbedding('invoice payment') const results = await table.search(embedding).limit(10).toArray() // Check that distances are reasonable (0 = perfect match, 2 = opposite) for (const result of results) { if (result._distance !== undefined) { expect(result._distance).toBeGreaterThanOrEqual(0) expect(result._distance).toBeLessThan(2) } } }) it('should return results sorted by relevance', async () => { if (!tables.includes('emails')) return const table = await db.openTable('emails') const embedding = await getEmbedding('project update') const results = await table.search(embedding).limit(5).toArray() // Results should be sorted by distance (ascending) for (let i = 1; i < results.length; i++) { if (results[i]._distance !== undefined && results[i-1]._distance !== undefined) { expect(results[i]._distance).toBeGreaterThanOrEqual(results[i-1]._distance) } } }) }) }) describe.skipIf(!indexExists)('Real Contacts Integration', () => { const ADDRESSBOOK_DIR = path.join(process.env.HOME, 'Library', 'Application Support', 'AddressBook') const contactsExist = fs.existsSync(ADDRESSBOOK_DIR) it.skipIf(!contactsExist)('should have AddressBook directory', () => { expect(fs.existsSync(ADDRESSBOOK_DIR)).toBe(true) }) }) // Report skip reason if index doesn't exist if (!indexExists) { console.log(` ⚠️ Real data tests skipped - LanceDB index not found at ${DB_PATH} To run these tests: 1. Start the MCP server to build the index 2. Wait for indexing to complete 3. Run tests again `) }

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