Skip to main content
Glama
incremental-indexing.test.js11.3 kB
/** * Integration tests for incremental indexing * Tests timestamp-based filtering for messages and emails */ import { describe, it, expect, beforeEach, vi } from 'vitest' // Mac Absolute Time epoch: Jan 1, 2001 00:00:00 UTC const MAC_ABSOLUTE_EPOCH = 978307200 describe('Incremental Indexing', () => { describe('timestamp-based message filtering', () => { // Simulate the getMessages filtering logic const filterMessagesBySinceTimestamp = (messages, sinceTimestamp) => { if (!sinceTimestamp) return messages // Convert Unix ms to Mac Absolute Time nanoseconds const macAbsoluteNs = (sinceTimestamp / 1000 - MAC_ABSOLUTE_EPOCH) * 1000000000 return messages.filter(m => { // Messages have dateTimestamp in Unix seconds const msgMacAbsoluteNs = (m.dateTimestamp - MAC_ABSOLUTE_EPOCH) * 1000000000 return msgMacAbsoluteNs >= macAbsoluteNs }) } it('should return all messages when sinceTimestamp is null', () => { const messages = [ { id: 1, dateTimestamp: MAC_ABSOLUTE_EPOCH + 1000 }, { id: 2, dateTimestamp: MAC_ABSOLUTE_EPOCH + 2000 }, { id: 3, dateTimestamp: MAC_ABSOLUTE_EPOCH + 3000 } ] const result = filterMessagesBySinceTimestamp(messages, null) expect(result).toHaveLength(3) }) it('should filter messages older than sinceTimestamp', () => { const messages = [ { id: 1, dateTimestamp: MAC_ABSOLUTE_EPOCH + 1000 }, // Old { id: 2, dateTimestamp: MAC_ABSOLUTE_EPOCH + 2000 }, // Old { id: 3, dateTimestamp: MAC_ABSOLUTE_EPOCH + 3000 } // New ] // sinceTimestamp in Unix ms - want messages from MAC_ABSOLUTE_EPOCH + 2500 onwards const sinceTimestamp = (MAC_ABSOLUTE_EPOCH + 2500) * 1000 const result = filterMessagesBySinceTimestamp(messages, sinceTimestamp) expect(result).toHaveLength(1) expect(result[0].id).toBe(3) }) it('should include messages exactly at sinceTimestamp', () => { const messages = [ { id: 1, dateTimestamp: MAC_ABSOLUTE_EPOCH + 2000 } ] const sinceTimestamp = (MAC_ABSOLUTE_EPOCH + 2000) * 1000 const result = filterMessagesBySinceTimestamp(messages, sinceTimestamp) expect(result).toHaveLength(1) }) it('should handle empty message list', () => { const result = filterMessagesBySinceTimestamp([], Date.now()) expect(result).toHaveLength(0) }) }) describe('metadata timestamp management', () => { // Simulate metadata loading/saving let mockMeta = {} const loadIndexMeta = () => ({ ...mockMeta }) const saveIndexMeta = (meta) => { mockMeta = { ...meta } } beforeEach(() => { mockMeta = {} }) it('should save lastMessageIndexTime after indexing', () => { const indexStartTime = Date.now() const meta = loadIndexMeta() saveIndexMeta({ ...meta, lastMessageIndexTime: indexStartTime }) const savedMeta = loadIndexMeta() expect(savedMeta.lastMessageIndexTime).toBe(indexStartTime) }) it('should preserve other metadata fields when saving', () => { mockMeta = { lastEmailIndexTime: 1234567890, otherField: 'value' } const meta = loadIndexMeta() const indexStartTime = Date.now() saveIndexMeta({ ...meta, lastMessageIndexTime: indexStartTime }) const savedMeta = loadIndexMeta() expect(savedMeta.lastEmailIndexTime).toBe(1234567890) expect(savedMeta.otherField).toBe('value') expect(savedMeta.lastMessageIndexTime).toBe(indexStartTime) }) it('should return empty object when no metadata exists', () => { mockMeta = {} const meta = loadIndexMeta() expect(meta).toEqual({}) }) }) describe('1-hour buffer logic', () => { const ONE_HOUR_MS = 60 * 60 * 1000 const calculateEffectiveTimestamp = (lastIndexTime, forceFullScan) => { if (forceFullScan) return null if (!lastIndexTime) return null return lastIndexTime - ONE_HOUR_MS } it('should return null for forceFullScan', () => { const result = calculateEffectiveTimestamp(Date.now(), true) expect(result).toBeNull() }) it('should return null when no previous timestamp', () => { const result = calculateEffectiveTimestamp(null, false) expect(result).toBeNull() }) it('should subtract 1 hour from previous timestamp', () => { const lastIndexTime = Date.now() const result = calculateEffectiveTimestamp(lastIndexTime, false) expect(result).toBe(lastIndexTime - ONE_HOUR_MS) }) it('should handle timestamps at epoch', () => { const lastIndexTime = ONE_HOUR_MS // 1 hour after epoch const result = calculateEffectiveTimestamp(lastIndexTime, false) expect(result).toBe(0) // Should be at epoch }) }) describe('full scan vs incremental scan detection', () => { const determinesScanType = (meta, forceFullScan) => { const ONE_HOUR_MS = 60 * 60 * 1000 const lastMessageIndexTime = forceFullScan ? null : (meta.lastMessageIndexTime ? meta.lastMessageIndexTime - ONE_HOUR_MS : null) return lastMessageIndexTime ? 'incremental' : 'full' } it('should do full scan on first run (no metadata)', () => { const scanType = determinesScanType({}, false) expect(scanType).toBe('full') }) it('should do full scan when forceFullScan is true', () => { const meta = { lastMessageIndexTime: Date.now() } const scanType = determinesScanType(meta, true) expect(scanType).toBe('full') }) it('should do incremental scan when metadata exists', () => { const meta = { lastMessageIndexTime: Date.now() } const scanType = determinesScanType(meta, false) expect(scanType).toBe('incremental') }) it('should do full scan when lastMessageIndexTime is 0', () => { const meta = { lastMessageIndexTime: 0 } const scanType = determinesScanType(meta, false) expect(scanType).toBe('full') }) }) describe('Mac Absolute Time conversion', () => { it('should correctly convert Unix ms to Mac Absolute nanoseconds', () => { // Known conversion: Jan 1, 2001 00:00:00 UTC = 978307200 Unix seconds const unixMs = MAC_ABSOLUTE_EPOCH * 1000 // Mac epoch in Unix ms const macAbsoluteNs = (unixMs / 1000 - MAC_ABSOLUTE_EPOCH) * 1000000000 expect(macAbsoluteNs).toBe(0) // Should be 0 at Mac epoch }) it('should handle current time correctly', () => { const now = Date.now() const macAbsoluteNs = (now / 1000 - MAC_ABSOLUTE_EPOCH) * 1000000000 // Should be positive (we're after Jan 1, 2001) expect(macAbsoluteNs).toBeGreaterThan(0) }) it('should handle timestamps before Mac epoch', () => { // Year 2000 - before Mac epoch const year2000 = new Date('2000-01-01T00:00:00Z').getTime() const macAbsoluteNs = (year2000 / 1000 - MAC_ABSOLUTE_EPOCH) * 1000000000 // Should be negative expect(macAbsoluteNs).toBeLessThan(0) }) it('should produce consistent round-trip conversion', () => { const originalUnixMs = Date.now() // Convert to Mac Absolute nanoseconds const macAbsoluteNs = (originalUnixMs / 1000 - MAC_ABSOLUTE_EPOCH) * 1000000000 // Convert back to Unix ms const convertedUnixMs = (macAbsoluteNs / 1000000000 + MAC_ABSOLUTE_EPOCH) * 1000 // Should be very close (within 1ms due to floating point) expect(Math.abs(convertedUnixMs - originalUnixMs)).toBeLessThan(1) }) }) describe('indexing workflow integration', () => { // Simulate the full indexing workflow const simulateIndexMessages = async (options) => { const { existingMessages, indexedIds, forceFullScan, lastMessageIndexTime } = options const ONE_HOUR_MS = 60 * 60 * 1000 const indexStartTime = Date.now() // Determine effective timestamp const effectiveTimestamp = forceFullScan ? null : (lastMessageIndexTime ? lastMessageIndexTime - ONE_HOUR_MS : null) // Filter messages by timestamp let messages = existingMessages if (effectiveTimestamp) { messages = existingMessages.filter(m => m.dateTimestamp * 1000 >= effectiveTimestamp ) } // Filter out already indexed const toIndex = messages.filter(m => !indexedIds.has(String(m.id))) return { scanType: effectiveTimestamp ? 'incremental' : 'full', messagesFound: messages.length, messagesToIndex: toIndex.length, newLastMessageIndexTime: indexStartTime } } it('should perform full scan and index all messages on first run', async () => { const result = await simulateIndexMessages({ existingMessages: [ { id: 1, dateTimestamp: Date.now() / 1000 - 86400 }, // 1 day ago { id: 2, dateTimestamp: Date.now() / 1000 - 3600 }, // 1 hour ago { id: 3, dateTimestamp: Date.now() / 1000 } // Now ], indexedIds: new Set(), forceFullScan: false, lastMessageIndexTime: null }) expect(result.scanType).toBe('full') expect(result.messagesFound).toBe(3) expect(result.messagesToIndex).toBe(3) }) it('should perform incremental scan on subsequent runs', async () => { const now = Date.now() const twoHoursAgo = now - 2 * 60 * 60 * 1000 const result = await simulateIndexMessages({ existingMessages: [ { id: 1, dateTimestamp: (now - 3 * 60 * 60 * 1000) / 1000 }, // 3 hours ago { id: 2, dateTimestamp: (now - 30 * 60 * 1000) / 1000 }, // 30 min ago { id: 3, dateTimestamp: now / 1000 } // Now ], indexedIds: new Set(['1']), // id 1 already indexed forceFullScan: false, lastMessageIndexTime: twoHoursAgo // Last indexed 2 hours ago }) expect(result.scanType).toBe('incremental') // With 1-hour buffer, should find messages from 3 hours ago onwards expect(result.messagesFound).toBe(3) // But only 2 need indexing (1 is already indexed) expect(result.messagesToIndex).toBe(2) }) it('should force full scan when requested', async () => { const now = Date.now() const result = await simulateIndexMessages({ existingMessages: [ { id: 1, dateTimestamp: (now - 7 * 24 * 60 * 60 * 1000) / 1000 }, // 7 days ago { id: 2, dateTimestamp: now / 1000 } ], indexedIds: new Set(['1']), forceFullScan: true, lastMessageIndexTime: now - 60 * 60 * 1000 // Has recent timestamp }) expect(result.scanType).toBe('full') expect(result.messagesFound).toBe(2) }) it('should skip already indexed messages', async () => { const result = await simulateIndexMessages({ existingMessages: [ { id: 1, dateTimestamp: Date.now() / 1000 }, { id: 2, dateTimestamp: Date.now() / 1000 }, { id: 3, dateTimestamp: Date.now() / 1000 } ], indexedIds: new Set(['1', '2', '3']), forceFullScan: false, lastMessageIndexTime: null }) expect(result.messagesToIndex).toBe(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