Skip to main content
Glama
performance.test.js12 kB
/** * Performance & Boundary Tests - Stress testing and performance validation * * Tests for: * - Large result sets * - Concurrent operations * - Response time boundaries * - Memory-efficient processing */ 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' import { loadContacts, searchContacts, lookupContact } from '../../contacts.js' // Real paths const DATA_DIR = path.join(process.env.HOME, '.apple-tools-mcp') const DB_PATH = path.join(DATA_DIR, 'vector-index') const indexExists = fs.existsSync(DB_PATH) let db = null 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) } async function searchTable(tableName, query, limit = 10) { if (!db) return [] try { const tables = await db.tableNames() if (!tables.includes(tableName)) return [] const table = await db.openTable(tableName) const embedding = await getEmbedding(query) return await table.search(embedding).limit(limit).toArray() } catch (e) { return [] } } // ============================================================================ // LARGE RESULT SET TESTS // ============================================================================ describe.skipIf(!indexExists)('Large Result Set Handling', () => { beforeAll(async () => { if (indexExists) { db = await connect(DB_PATH) } }) it('should handle 100 result limit efficiently', async () => { const start = Date.now() const results = await searchTable('emails', 'the', 100) const elapsed = Date.now() - start console.log(` → Retrieved ${results.length} emails in ${elapsed}ms`) expect(results.length).toBeLessThanOrEqual(100) expect(elapsed).toBeLessThan(10000) // Should complete within 10 seconds }) it('should handle 200 result limit', async () => { const start = Date.now() const results = await searchTable('emails', 'meeting', 200) const elapsed = Date.now() - start console.log(` → Retrieved ${results.length} emails in ${elapsed}ms`) expect(results.length).toBeLessThanOrEqual(200) expect(elapsed).toBeLessThan(15000) }) it('should handle large message queries', async () => { const start = Date.now() const results = await searchTable('messages', 'hi', 100) const elapsed = Date.now() - start console.log(` → Retrieved ${results.length} messages in ${elapsed}ms`) expect(elapsed).toBeLessThan(10000) }) it('should handle large calendar queries', async () => { const start = Date.now() const results = await searchTable('calendar', 'meeting', 100) const elapsed = Date.now() - start console.log(` → Retrieved ${results.length} calendar events in ${elapsed}ms`) expect(elapsed).toBeLessThan(10000) }) }) // ============================================================================ // CONCURRENT OPERATION TESTS // ============================================================================ describe.skipIf(!indexExists)('Concurrent Operations', () => { beforeAll(async () => { if (indexExists && !db) { db = await connect(DB_PATH) } }) it('should handle 3 concurrent email searches', async () => { const start = Date.now() const [results1, results2, results3] = await Promise.all([ searchTable('emails', 'meeting', 10), searchTable('emails', 'project', 10), searchTable('emails', 'deadline', 10) ]) const elapsed = Date.now() - start console.log(` → 3 concurrent searches completed in ${elapsed}ms`) console.log(` → Results: ${results1.length}, ${results2.length}, ${results3.length}`) expect(Array.isArray(results1)).toBe(true) expect(Array.isArray(results2)).toBe(true) expect(Array.isArray(results3)).toBe(true) // Concurrent should be faster than sequential (or at least not crash) expect(elapsed).toBeLessThan(30000) }) it('should handle concurrent searches across different tables', async () => { const start = Date.now() const [emails, messages, calendar] = await Promise.all([ searchTable('emails', 'update', 10), searchTable('messages', 'update', 10), searchTable('calendar', 'update', 10) ]) const elapsed = Date.now() - start console.log(` → Cross-table concurrent search in ${elapsed}ms`) expect(Array.isArray(emails)).toBe(true) expect(Array.isArray(messages)).toBe(true) expect(Array.isArray(calendar)).toBe(true) }) it('should handle 5 concurrent searches', async () => { const queries = ['invoice', 'receipt', 'payment', 'order', 'confirmation'] const start = Date.now() const results = await Promise.all( queries.map(q => searchTable('emails', q, 10)) ) const elapsed = Date.now() - start console.log(` → 5 concurrent searches completed in ${elapsed}ms`) results.forEach((r, i) => { expect(Array.isArray(r)).toBe(true) console.log(` → "${queries[i]}": ${r.length} results`) }) }) }) // ============================================================================ // RESPONSE TIME TESTS // ============================================================================ describe.skipIf(!indexExists)('Response Time Boundaries', () => { beforeAll(async () => { if (indexExists && !db) { db = await connect(DB_PATH) } }) it('should return first 10 results within 5 seconds', async () => { const start = Date.now() const results = await searchTable('emails', 'important', 10) const elapsed = Date.now() - start console.log(` → 10 results in ${elapsed}ms`) expect(elapsed).toBeLessThan(5000) }) it('should handle obscure query quickly', async () => { const start = Date.now() const results = await searchTable('emails', 'xyznonexistentquery99999', 10) const elapsed = Date.now() - start console.log(` → Obscure query result in ${elapsed}ms, found ${results.length} results`) // Vector search always returns nearest neighbors, even for nonsense queries // The important thing is that it completes quickly expect(elapsed).toBeLessThan(5000) expect(Array.isArray(results)).toBe(true) }) it('should handle repeated queries efficiently (caching)', async () => { // First query (cold) const start1 = Date.now() await searchTable('emails', 'test query for cache', 10) const elapsed1 = Date.now() - start1 // Second query (potentially cached) const start2 = Date.now() await searchTable('emails', 'test query for cache', 10) const elapsed2 = Date.now() - start2 console.log(` → First query: ${elapsed1}ms, Second query: ${elapsed2}ms`) // Second should be same or faster (caching may help) expect(elapsed2).toBeLessThanOrEqual(elapsed1 * 1.5) // Allow some variance }) }) // ============================================================================ // CONTACT PERFORMANCE TESTS // ============================================================================ describe('Contact Operations Performance', () => { beforeAll(() => { loadContacts() }) it('should handle rapid lookups', () => { const start = Date.now() // Perform 100 lookups for (let i = 0; i < 100; i++) { lookupContact('john@example.com') } const elapsed = Date.now() - start console.log(` → 100 contact lookups in ${elapsed}ms`) expect(elapsed).toBeLessThan(1000) // Should be very fast }) it('should handle rapid searches', () => { const start = Date.now() // Perform 50 searches for (let i = 0; i < 50; i++) { searchContacts('john', 10) } const elapsed = Date.now() - start console.log(` → 50 contact searches in ${elapsed}ms`) expect(elapsed).toBeLessThan(1000) }) it('should search entire contact database efficiently', () => { const start = Date.now() const results = searchContacts('a', 1000) // Wide search const elapsed = Date.now() - start console.log(` → Full contact search: ${results.length} results in ${elapsed}ms`) expect(elapsed).toBeLessThan(500) }) }) // ============================================================================ // MEMORY EFFICIENCY TESTS // ============================================================================ describe.skipIf(!indexExists)('Memory Efficiency', () => { beforeAll(async () => { if (indexExists && !db) { db = await connect(DB_PATH) } }) it('should not accumulate memory over repeated queries', async () => { // Run multiple queries and check we can complete without issues for (let i = 0; i < 20; i++) { await searchTable('emails', `query ${i}`, 10) } // If we get here without crashing, memory is managed expect(true).toBe(true) }) it('should handle alternating table queries', async () => { for (let i = 0; i < 10; i++) { await searchTable('emails', 'test', 5) await searchTable('messages', 'test', 5) await searchTable('calendar', 'test', 5) } expect(true).toBe(true) }) }) // ============================================================================ // QUERY EXPANSION TESTS // ============================================================================ describe('Query Processing Performance', () => { it('should handle query with many synonyms efficiently', async () => { const complexQuery = 'meeting appointment call sync discussion conference chat talk' if (indexExists && !db) { db = await connect(DB_PATH) } if (db) { const start = Date.now() const results = await searchTable('emails', complexQuery, 10) const elapsed = Date.now() - start console.log(` → Complex synonym query: ${elapsed}ms, ${results.length} results`) expect(elapsed).toBeLessThan(10000) } else { expect(true).toBe(true) } }) it('should handle query with negations efficiently', async () => { const negationQuery = 'meeting NOT monday NOT tuesday' if (indexExists && db) { const start = Date.now() const results = await searchTable('emails', negationQuery, 10) const elapsed = Date.now() - start console.log(` → Negation query: ${elapsed}ms, ${results.length} results`) expect(elapsed).toBeLessThan(10000) } else { expect(true).toBe(true) } }) }) // ============================================================================ // BATCH OPERATION TESTS // ============================================================================ describe.skipIf(!indexExists)('Batch Operations', () => { beforeAll(async () => { if (indexExists && !db) { db = await connect(DB_PATH) } }) it('should handle sequential batch of queries', async () => { const queries = [ 'invoice', 'receipt', 'payment', 'order', 'shipping', 'meeting', 'call', 'conference', 'sync', 'standup' ] const start = Date.now() for (const query of queries) { await searchTable('emails', query, 5) } const elapsed = Date.now() - start const avgPerQuery = elapsed / queries.length console.log(` → 10 sequential queries: ${elapsed}ms total, ${avgPerQuery.toFixed(1)}ms avg`) expect(avgPerQuery).toBeLessThan(3000) }) it('should handle mixed source batch queries', async () => { const sources = ['emails', 'messages', 'calendar'] const query = 'update' const start = Date.now() for (let i = 0; i < 9; i++) { const source = sources[i % 3] await searchTable(source, query, 5) } const elapsed = Date.now() - start console.log(` → 9 mixed-source queries: ${elapsed}ms`) expect(elapsed).toBeLessThan(30000) }) })

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