Skip to main content
Glama
orchestration.test.js10.8 kB
/** * Unit tests for index.js orchestration functions * Tests: checkIfFirstRun, runIndexCycle, startBackgroundIndexing, stopBackgroundIndexing */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { createLockFileMock, createLanceDBMock } from '../helpers/indexing-mocks.js' describe('checkIfFirstRun', () => { describe('no tables exist', () => { it('should return true when no index tables exist', async () => { const lancedb = createLanceDBMock() // Mock isIndexReady to return false for all tables const emailsReady = false const messagesReady = false const calendarReady = false // If none are ready, it's first run const isFirstRun = !(emailsReady || messagesReady || calendarReady) expect(isFirstRun).toBe(true) }) it('should check all three table types', async () => { const tableTypes = ['emails', 'messages', 'calendar'] expect(tableTypes.length).toBe(3) expect(tableTypes).toContain('emails') expect(tableTypes).toContain('messages') expect(tableTypes).toContain('calendar') }) }) describe('any table exists', () => { it('should return false when emails table exists', async () => { const emailsReady = true const messagesReady = false const calendarReady = false const isFirstRun = !(emailsReady || messagesReady || calendarReady) expect(isFirstRun).toBe(false) }) it('should return false when messages table exists', async () => { const emailsReady = false const messagesReady = true const calendarReady = false const isFirstRun = !(emailsReady || messagesReady || calendarReady) expect(isFirstRun).toBe(false) }) it('should return false when calendar table exists', async () => { const emailsReady = false const messagesReady = false const calendarReady = true const isFirstRun = !(emailsReady || messagesReady || calendarReady) expect(isFirstRun).toBe(false) }) it('should return false when all tables exist', async () => { const emailsReady = true const messagesReady = true const calendarReady = true const isFirstRun = !(emailsReady || messagesReady || calendarReady) expect(isFirstRun).toBe(false) }) }) }) describe('runIndexCycle', () => { let lockMock beforeEach(() => { vi.clearAllMocks() lockMock = createLockFileMock() }) describe('indexingInProgress flag', () => { it('should skip if indexingInProgress is true', () => { let indexingInProgress = true let indexAllCalled = false // Simulate runIndexCycle behavior if (indexingInProgress) { // Skip - "Indexing already in progress, skipping cycle" } else { indexAllCalled = true } expect(indexAllCalled).toBe(false) }) it('should proceed if indexingInProgress is false', () => { let indexingInProgress = false let indexAllCalled = false if (!indexingInProgress) { indexAllCalled = true } expect(indexAllCalled).toBe(true) }) it('should set indexingInProgress to true before indexing', () => { let indexingInProgress = false // Simulate runIndexCycle behavior indexingInProgress = true expect(indexingInProgress).toBe(true) }) it('should set indexingInProgress to false after completion', async () => { let indexingInProgress = true // Simulate completion await Promise.resolve() // Simulate async indexAll indexingInProgress = false expect(indexingInProgress).toBe(false) }) }) describe('lock acquisition', () => { it('should acquire lock before indexing', () => { lockMock.acquire(process.pid) expect(lockMock.isLocked()).toBe(true) expect(lockMock.getHolder()).toBe(process.pid) }) it('should skip if lock acquisition fails', () => { // Another process holds the lock lockMock.acquire(12345) // Different PID const acquired = lockMock.acquire(process.pid) expect(acquired).toBe(false) }) it('should release lock after completion', () => { lockMock.acquire(process.pid) expect(lockMock.isLocked()).toBe(true) lockMock.release(process.pid) expect(lockMock.isLocked()).toBe(false) }) it('should release lock even on error', async () => { lockMock.acquire(process.pid) try { throw new Error('Indexing error') } catch { lockMock.release(process.pid) } expect(lockMock.isLocked()).toBe(false) }) }) describe('state updates', () => { it('should update lastIndexTime after success', async () => { let lastIndexTime = 0 const beforeTime = Date.now() // Simulate successful indexing await Promise.resolve() lastIndexTime = Date.now() expect(lastIndexTime).toBeGreaterThanOrEqual(beforeTime) }) it('should set isFirstEverRun to false after success', async () => { let isFirstEverRun = true // Simulate successful indexing await Promise.resolve() isFirstEverRun = false expect(isFirstEverRun).toBe(false) }) it('should set sessionIndexComplete to true after success', async () => { let sessionIndexComplete = false // Simulate successful indexing await Promise.resolve() sessionIndexComplete = true expect(sessionIndexComplete).toBe(true) }) it('should set sessionIndexComplete to true even on error', async () => { let sessionIndexComplete = false try { throw new Error('Indexing error') } catch { sessionIndexComplete = true } expect(sessionIndexComplete).toBe(true) }) }) }) describe('startBackgroundIndexing', () => { beforeEach(() => { vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() }) describe('immediate execution', () => { it('should run indexing immediately on call', () => { let runCount = 0 const runIndexCycle = () => { runCount++ } // Simulate startBackgroundIndexing behavior runIndexCycle() // Run immediately expect(runCount).toBe(1) }) }) describe('interval scheduling', () => { it('should set interval for INDEX_INTERVAL', () => { const INDEX_INTERVAL = 5 * 60 * 1000 // 5 minutes let runCount = 0 const runIndexCycle = () => { runCount++ } runIndexCycle() // Immediate setInterval(runIndexCycle, INDEX_INTERVAL) // Advance time by INDEX_INTERVAL vi.advanceTimersByTime(INDEX_INTERVAL) expect(runCount).toBe(2) }) it('should respect custom INDEX_INTERVAL_MS', () => { const customInterval = 60 * 1000 // 1 minute let runCount = 0 const runIndexCycle = () => { runCount++ } runIndexCycle() setInterval(runIndexCycle, customInterval) // Advance by custom interval vi.advanceTimersByTime(customInterval) expect(runCount).toBe(2) // Advance again vi.advanceTimersByTime(customInterval) expect(runCount).toBe(3) }) it('should run multiple cycles over time', () => { const INDEX_INTERVAL = 5 * 60 * 1000 let runCount = 0 const runIndexCycle = () => { runCount++ } runIndexCycle() setInterval(runIndexCycle, INDEX_INTERVAL) // Advance time by 3 intervals vi.advanceTimersByTime(INDEX_INTERVAL * 3) expect(runCount).toBe(4) // Initial + 3 intervals }) }) }) describe('stopBackgroundIndexing', () => { beforeEach(() => { vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() }) describe('interval cleanup', () => { it('should clear the interval timer', () => { const INDEX_INTERVAL = 5 * 60 * 1000 let runCount = 0 const runIndexCycle = () => { runCount++ } runIndexCycle() const timer = setInterval(runIndexCycle, INDEX_INTERVAL) // Stop the timer clearInterval(timer) // Advance time vi.advanceTimersByTime(INDEX_INTERVAL * 2) // Should only have the initial run expect(runCount).toBe(1) }) it('should handle multiple stop calls gracefully', () => { let indexTimer = null const stopBackgroundIndexing = () => { if (indexTimer) { clearInterval(indexTimer) indexTimer = null } } indexTimer = setInterval(() => {}, 1000) // Call stop multiple times stopBackgroundIndexing() stopBackgroundIndexing() stopBackgroundIndexing() expect(indexTimer).toBeNull() }) it('should be safe to call when no timer exists', () => { let indexTimer = null const stopBackgroundIndexing = () => { if (indexTimer) { clearInterval(indexTimer) indexTimer = null } } // Should not throw expect(() => stopBackgroundIndexing()).not.toThrow() }) }) }) describe('initializeIndexing', () => { it('should check first run status', async () => { let isFirstEverRun = false let checkCalled = false const checkIfFirstRun = async () => { checkCalled = true return true } isFirstEverRun = await checkIfFirstRun() expect(checkCalled).toBe(true) expect(isFirstEverRun).toBe(true) }) it('should acquire lock before starting', () => { const lockMock = createLockFileMock() const acquired = lockMock.acquire(process.pid) expect(acquired).toBe(true) expect(lockMock.isLocked()).toBe(true) }) it('should exit if another instance holds lock', () => { const lockMock = createLockFileMock() // Another instance holds the lock lockMock.acquire(99999) const acquired = lockMock.acquire(process.pid) expect(acquired).toBe(false) // In real implementation, this would call process.exit(0) }) }) describe('getIndexingMessage', () => { it('should return first run message when isFirstEverRun is true', () => { const isFirstEverRun = true const message = isFirstEverRun ? 'Building initial index. This may take several minutes on first run. Please try again shortly.' : 'Indexing new data. Please try again in a moment.' expect(message).toContain('initial index') expect(message).toContain('several minutes') }) it('should return incremental message when isFirstEverRun is false', () => { const isFirstEverRun = false const message = isFirstEverRun ? 'Building initial index. This may take several minutes on first run. Please try again shortly.' : 'Indexing new data. Please try again in a moment.' expect(message).toContain('Indexing new data') }) })

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