Skip to main content
Glama
metadata-handling.test.js7.29 kB
/** * Unit tests for index metadata handling * Tests: loadIndexMeta, saveIndexMeta, metadata file operations */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { createIndexMetaMock, createEmailFileSystemMock } from '../helpers/indexing-mocks.js' import path from 'path' describe('loadIndexMeta', () => { let fsMock beforeEach(() => { vi.clearAllMocks() fsMock = createEmailFileSystemMock() }) afterEach(() => { vi.restoreAllMocks() }) describe('missing file handling', () => { it('should return empty object when file does not exist', () => { // Simulate file not existing fsMock.existsSync.mockReturnValue(false) // loadIndexMeta should return {} when file doesn't exist const result = fsMock.existsSync('/nonexistent/path') ? JSON.parse(fsMock.readFileSync('/nonexistent/path')) : {} expect(result).toEqual({}) }) }) describe('valid JSON parsing', () => { it('should parse valid JSON from file', () => { const expectedMeta = { lastEmailIndexTime: Date.now() - 3600000, version: '1.0' } fsMock.existsSync.mockReturnValue(true) fsMock.readFileSync.mockReturnValue(JSON.stringify(expectedMeta)) const result = JSON.parse(fsMock.readFileSync('/path/to/meta.json')) expect(result).toEqual(expectedMeta) expect(result.lastEmailIndexTime).toBeDefined() }) it('should handle timestamps correctly', () => { const timestamp = Date.now() const meta = { lastEmailIndexTime: timestamp } fsMock.readFileSync.mockReturnValue(JSON.stringify(meta)) const result = JSON.parse(fsMock.readFileSync('/path/to/meta.json')) expect(result.lastEmailIndexTime).toBe(timestamp) expect(typeof result.lastEmailIndexTime).toBe('number') }) }) describe('error handling', () => { it('should return empty object on parse error', () => { fsMock.existsSync.mockReturnValue(true) fsMock.readFileSync.mockReturnValue('invalid json {{{') let result = {} try { result = JSON.parse(fsMock.readFileSync('/path/to/meta.json')) } catch { result = {} } expect(result).toEqual({}) }) it('should return empty object on read error', () => { fsMock.existsSync.mockReturnValue(true) fsMock.readFileSync.mockImplementation(() => { throw new Error('EACCES: permission denied') }) let result = {} try { result = JSON.parse(fsMock.readFileSync('/path/to/meta.json')) } catch { result = {} } expect(result).toEqual({}) }) }) }) describe('saveIndexMeta', () => { let fsMock beforeEach(() => { vi.clearAllMocks() fsMock = createEmailFileSystemMock() }) afterEach(() => { vi.restoreAllMocks() }) describe('directory creation', () => { it('should create directory if not exists', () => { fsMock.existsSync.mockReturnValue(false) // Simulate saveIndexMeta behavior const filePath = '/Users/test/.apple-tools-mcp/index-meta.json' const dir = path.dirname(filePath) if (!fsMock.existsSync(dir)) { fsMock.mkdirSync(dir, { recursive: true }) } expect(fsMock.mkdirSync).toHaveBeenCalledWith(dir, { recursive: true }) }) it('should not recreate existing directory', () => { fsMock.existsSync.mockReturnValue(true) const filePath = '/Users/test/.apple-tools-mcp/index-meta.json' const dir = path.dirname(filePath) if (!fsMock.existsSync(dir)) { fsMock.mkdirSync(dir, { recursive: true }) } expect(fsMock.mkdirSync).not.toHaveBeenCalled() }) }) describe('JSON formatting', () => { it('should write formatted JSON', () => { const meta = { lastEmailIndexTime: 1234567890 } // Simulate saveIndexMeta behavior const formatted = JSON.stringify(meta, null, 2) fsMock.writeFileSync('/path/to/meta.json', formatted) expect(fsMock.writeFileSync).toHaveBeenCalledWith( '/path/to/meta.json', expect.stringContaining('"lastEmailIndexTime"') ) // Verify it's properly indented expect(formatted).toContain('\n') }) it('should handle nested objects', () => { const meta = { lastEmailIndexTime: Date.now(), stats: { emails: 100, messages: 50 } } const formatted = JSON.stringify(meta, null, 2) expect(formatted).toContain('"stats"') expect(formatted).toContain('"emails"') }) }) describe('field preservation', () => { it('should preserve existing metadata fields when updating', () => { const existingMeta = { lastEmailIndexTime: 1000000, customField: 'preserved' } const newMeta = { ...existingMeta, lastEmailIndexTime: 2000000 } expect(newMeta.customField).toBe('preserved') expect(newMeta.lastEmailIndexTime).toBe(2000000) }) it('should not overwrite other keys when updating single field', () => { const metaMock = createIndexMetaMock({ lastEmailIndexTime: 1000000, version: '1.0', indexedCount: 500 }) metaMock.save({ lastEmailIndexTime: 2000000 }) const result = metaMock.getMeta() expect(result.lastEmailIndexTime).toBe(2000000) expect(result.version).toBe('1.0') expect(result.indexedCount).toBe(500) }) }) describe('atomic write considerations', () => { it('should write complete JSON in single call', () => { const meta = { lastEmailIndexTime: Date.now() } fsMock.writeFileSync('/path/to/meta.json', JSON.stringify(meta, null, 2)) // Verify single write call expect(fsMock.writeFileSync).toHaveBeenCalledTimes(1) }) }) }) describe('IndexMetaMock', () => { it('should track metadata changes', () => { const mock = createIndexMetaMock({ initial: true }) mock.save({ updated: true }) const result = mock.getMeta() expect(result.initial).toBe(true) expect(result.updated).toBe(true) }) it('should reset to initial state', () => { const mock = createIndexMetaMock({ original: 'value' }) mock.save({ new: 'data' }) mock.reset() const result = mock.getMeta() expect(result).toEqual({ original: 'value' }) expect(result.new).toBeUndefined() }) }) describe('metadata timestamp handling', () => { it('should save current timestamp before indexing starts', () => { const indexStartTime = Date.now() // This ensures any emails arriving during indexing // will be picked up on the next incremental scan const meta = { lastEmailIndexTime: indexStartTime } expect(meta.lastEmailIndexTime).toBe(indexStartTime) expect(meta.lastEmailIndexTime).toBeLessThanOrEqual(Date.now()) }) it('should apply 1-day buffer for incremental scans', () => { const ONE_DAY_MS = 24 * 60 * 60 * 1000 const lastIndexTime = Date.now() - (2 * ONE_DAY_MS) // The implementation applies a 1-day buffer to catch edge cases const effectiveTime = lastIndexTime - ONE_DAY_MS expect(effectiveTime).toBeLessThan(lastIndexTime) expect(lastIndexTime - effectiveTime).toBe(ONE_DAY_MS) }) })

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