Skip to main content
Glama
reindex-workflow.test.js7.92 kB
/** * Integration tests for rebuild_index tool workflow */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { createIndexerMocks, createLanceDBMock, createLockFileMock } from '../helpers/indexing-mocks.js' import { generateTestEmails, generateTestMessages, generateCalendarEvents } from '../helpers/test-data-generators.js' describe('Rebuild Index Tool', () => { let mocks let lockMock beforeEach(() => { vi.clearAllMocks() mocks = createIndexerMocks() lockMock = createLockFileMock() }) afterEach(() => { vi.restoreAllMocks() }) describe('source clearing', () => { it('should clear specified sources before rebuilding', async () => { const lancedb = createLanceDBMock() const db = await lancedb.connect() // Setup existing tables await db.createTable('emails', [{ filePath: 'old', vector: new Array(384).fill(0) }]) await db.createTable('messages', [{ id: '1', vector: new Array(384).fill(0) }]) // Clear only emails await db.dropTable('emails') const tables = await db.tableNames() expect(tables).not.toContain('emails') expect(tables).toContain('messages') }) it('should rebuild all sources when none specified', async () => { const sources = ['emails', 'messages', 'calendar'] const defaultSources = sources // When no sources specified, use all expect(defaultSources).toEqual(['emails', 'messages', 'calendar']) expect(defaultSources).toHaveLength(3) }) it('should only clear requested sources', async () => { const requestedSources = ['emails'] const lancedb = createLanceDBMock() const db = await lancedb.connect() // Setup all tables await db.createTable('emails', [{ filePath: 'test', vector: new Array(384).fill(0) }]) await db.createTable('messages', [{ id: '1', vector: new Array(384).fill(0) }]) await db.createTable('calendar', [{ id: 'event', vector: new Array(384).fill(0) }]) // Clear only requested sources for (const source of requestedSources) { await db.dropTable(source) } const tables = await db.tableNames() expect(tables).not.toContain('emails') expect(tables).toContain('messages') expect(tables).toContain('calendar') }) }) describe('full scan forcing', () => { it('should force full scan for email rebuild', () => { // rebuildIndex calls indexEmails with forceFullScan = true const forceFullScan = true expect(forceFullScan).toBe(true) }) it('should ignore lastEmailIndexTime when forcing full scan', () => { const meta = { lastEmailIndexTime: Date.now() - 3600000 } const forceFullScan = true // When forceFullScan is true, lastEmailIndexTime should be treated as null const effectiveTime = forceFullScan ? null : meta.lastEmailIndexTime expect(effectiveTime).toBeNull() }) }) describe('lock acquisition', () => { it('should acquire lock during rebuild', () => { lockMock.acquire(process.pid) expect(lockMock.isLocked()).toBe(true) }) it('should prevent concurrent rebuilds', () => { // First rebuild acquires lock lockMock.acquire(process.pid) // Second rebuild attempt fails const secondAttempt = lockMock.acquire(process.pid + 1) // Should fail (lock held by different PID logic varies, but concept is tested) expect(lockMock.isLocked()).toBe(true) }) it('should release lock after rebuild completes', async () => { lockMock.acquire(process.pid) // Simulate rebuild completion await Promise.resolve() lockMock.release(process.pid) expect(lockMock.isLocked()).toBe(false) }) }) describe('error tracking', () => { it('should track errors per source', () => { const results = { cleared: { emails: true, messages: true, calendar: false }, indexed: { emails: { indexed: 0, added: 50 }, messages: { indexed: 0, added: 20 } }, errors: [ { source: 'calendar', phase: 'clear', error: 'Permission denied' } ] } expect(results.errors).toHaveLength(1) expect(results.errors[0].source).toBe('calendar') expect(results.errors[0].phase).toBe('clear') }) it('should continue with other sources after error', () => { const sources = ['emails', 'messages', 'calendar'] const results = { cleared: {}, indexed: {}, errors: [] } for (const source of sources) { try { if (source === 'messages') { throw new Error('Test error') } results.cleared[source] = true results.indexed[source] = { added: 10 } } catch (e) { results.errors.push({ source, error: e.message }) } } expect(results.cleared.emails).toBe(true) expect(results.cleared.calendar).toBe(true) expect(results.errors).toHaveLength(1) expect(results.errors[0].source).toBe('messages') }) }) describe('fire and forget behavior', () => { it('should return immediately with status message', async () => { let responseReturned = false let indexingStarted = false // Simulate fire and forget const startRebuild = async () => { responseReturned = true // Rebuild runs in background setTimeout(() => { indexingStarted = true }, 10) } await startRebuild() expect(responseReturned).toBe(true) // Indexing may not have started yet (async) }) it('should return "rebuild started" message immediately', () => { const response = { content: [{ type: 'text', text: 'Index rebuild started for: emails, messages, calendar' }] } expect(response.content[0].text).toContain('rebuild started') }) }) describe('concurrent rebuild handling', () => { it('should handle concurrent rebuild requests', async () => { const results = [] // First request const request1 = async () => { if (lockMock.acquire(1)) { await Promise.resolve() results.push({ id: 1, status: 'completed' }) lockMock.release(1) } else { results.push({ id: 1, status: 'already in progress' }) } } // Second concurrent request const request2 = async () => { if (lockMock.acquire(2)) { await Promise.resolve() results.push({ id: 2, status: 'completed' }) lockMock.release(2) } else { results.push({ id: 2, status: 'already in progress' }) } } await request1() await request2() // Both should complete since they run sequentially in this test expect(results).toHaveLength(2) }) it('should return appropriate message when already in progress', () => { lockMock.acquire(process.pid) const canAcquire = lockMock.acquire(process.pid + 1) if (!canAcquire) { const message = 'Index rebuild already in progress. Please wait for completion.' expect(message).toContain('already in progress') } }) }) describe('result structure', () => { it('should return complete result structure', () => { const result = { cleared: { emails: true, messages: true, calendar: true }, indexed: { emails: { indexed: 0, added: 100 }, messages: { indexed: 0, added: 50 }, calendar: { indexed: 0, added: 25, removed: 0 } }, errors: [] } expect(result.cleared).toBeDefined() expect(result.indexed).toBeDefined() expect(result.errors).toBeDefined() expect(result.errors).toHaveLength(0) }) }) })

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