Skip to main content
Glama
batch.test.ts8.61 kB
import { jest, describe, it, expect, beforeEach } from '@jest/globals'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { registerBatchTools } from '../../tools/batch.js'; import { FirestoreClient } from '../../firestore/client.js'; import { PermissionManager } from '../../permissions/manager.js'; // Mock dependencies jest.mock('../../firestore/client.js'); jest.mock('../../permissions/manager.js'); describe('Batch Tools', () => { let server: McpServer; let firestoreClient: FirestoreClient; let permissionManager: PermissionManager; let registeredTools: Map<string, any>; let mockBatch: any; beforeEach(() => { // Create mocks mockBatch = { set: jest.fn(), update: jest.fn(), delete: jest.fn(), commit: jest.fn().mockResolvedValue(undefined) }; firestoreClient = { firestore: { batch: jest.fn(() => mockBatch), collection: jest.fn(() => ({ doc: jest.fn((id?: string) => ({ id: id || 'generated-id', path: `test/${id || 'generated-id'}` })) })), doc: jest.fn((path: string) => ({ id: path.split('/').pop(), path })), runTransaction: jest.fn() } } as any; permissionManager = { hasPermission: jest.fn().mockReturnValue(true) } as any; // Create a mock server that captures tool registrations registeredTools = new Map(); server = { tool: jest.fn((name, description, schema, handler) => { registeredTools.set(name, { description, schema, handler }); }) } as any; // Register the tools registerBatchTools(server, firestoreClient, permissionManager); }); describe('firestore-batch-write', () => { let batchWriteTool: any; beforeEach(() => { batchWriteTool = registeredTools.get('firestore-batch-write'); }); it('should be registered with correct metadata', () => { expect(batchWriteTool).toBeDefined(); expect(batchWriteTool.description).toBe('Execute multiple write operations in a single atomic batch'); }); it('should execute create operations', async () => { const operations = [ { type: 'create', collectionPath: 'users', documentId: 'user1', data: { name: 'John' } }, { type: 'create', collectionPath: 'users', data: { name: 'Jane' } } ]; const result = await batchWriteTool.handler({ operations }); expect(mockBatch.set).toHaveBeenCalledTimes(2); expect(mockBatch.commit).toHaveBeenCalled(); expect(result.isError).toBeUndefined(); const response = JSON.parse(result.content[0].text); expect(response.success).toBe(true); expect(response.operationCount).toBe(2); }); it('should execute mixed operations', async () => { const operations = [ { type: 'update', documentPath: 'users/user1', data: { age: 30 } }, { type: 'delete', documentPath: 'users/user2' } ]; const result = await batchWriteTool.handler({ operations }); expect(mockBatch.update).toHaveBeenCalledTimes(1); expect(mockBatch.delete).toHaveBeenCalledTimes(1); expect(mockBatch.commit).toHaveBeenCalled(); expect(result.isError).toBeUndefined(); }); it('should check permissions for all operations', async () => { (permissionManager.hasPermission as jest.Mock) = jest.fn((collection, operation) => { return collection !== 'restricted'; }); const operations = [ { type: 'create', collectionPath: 'restricted', data: { secret: 'data' } } ]; const result = await batchWriteTool.handler({ operations }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Access denied'); expect(mockBatch.commit).not.toHaveBeenCalled(); }); }); describe('firestore-batch-read', () => { let batchReadTool: any; beforeEach(() => { batchReadTool = registeredTools.get('firestore-batch-read'); }); it('should be registered with correct metadata', () => { expect(batchReadTool).toBeDefined(); expect(batchReadTool.description).toBe('Read multiple documents in a single operation'); }); it('should read multiple documents', async () => { const mockDocs: Record<string, any> = { 'users/user1': { exists: true, data: () => ({ name: 'John' }) }, 'users/user2': { exists: false } }; (firestoreClient.firestore.doc as jest.Mock) = jest.fn((path: string) => ({ get: jest.fn().mockResolvedValue(mockDocs[path]) })); const result = await batchReadTool.handler({ documentPaths: ['users/user1', 'users/user2'] }); expect(result.isError).toBeUndefined(); const response = JSON.parse(result.content[0].text); expect(response).toEqual([ { path: 'users/user1', exists: true, data: { name: 'John' } }, { path: 'users/user2', exists: false, data: null } ]); }); }); describe('firestore-transaction', () => { let transactionTool: any; beforeEach(() => { transactionTool = registeredTools.get('firestore-transaction'); }); it('should be registered with correct metadata', () => { expect(transactionTool).toBeDefined(); expect(transactionTool.description).toBe('Execute a transaction with read and write operations'); }); it('should execute transaction with reads and writes', async () => { const mockTransaction = { get: jest.fn((docRef) => Promise.resolve({ exists: true, data: () => ({ value: 100 }) })), set: jest.fn(), update: jest.fn(), delete: jest.fn() }; (firestoreClient.firestore.runTransaction as jest.Mock) = jest.fn(async (callback) => { return await callback(mockTransaction); }); const result = await transactionTool.handler({ reads: ['counters/counter1'], operations: [ { type: 'update', documentPath: 'counters/counter1', data: { value: 101 } } ] }); expect(result.isError).toBeUndefined(); expect(mockTransaction.get).toHaveBeenCalled(); expect(mockTransaction.update).toHaveBeenCalled(); }); it('should evaluate conditions in transactions', async () => { const mockTransaction = { get: jest.fn((docRef) => Promise.resolve({ exists: true, data: () => ({ balance: 50 }) })), update: jest.fn() }; (firestoreClient.firestore.runTransaction as jest.Mock) = jest.fn(async (callback) => { return await callback(mockTransaction); }); const result = await transactionTool.handler({ reads: ['users/user1'], operations: [ { type: 'update', documentPath: 'users/user1', data: { balance: 40 } } ], conditionScript: 'return readResults["users/user1"].data.balance >= 10;' }); expect(result.isError).toBeUndefined(); expect(mockTransaction.update).toHaveBeenCalled(); }); it('should fail when condition is not met', async () => { const mockTransaction = { get: jest.fn((docRef) => Promise.resolve({ exists: true, data: () => ({ balance: 5 }) })), update: jest.fn() }; (firestoreClient.firestore.runTransaction as jest.Mock) = jest.fn(async (callback) => { return await callback(mockTransaction); }); const result = await transactionTool.handler({ reads: ['users/user1'], operations: [ { type: 'update', documentPath: 'users/user1', data: { balance: -5 } } ], conditionScript: 'return readResults["users/user1"].data.balance >= 10;' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Transaction condition failed'); expect(mockTransaction.update).not.toHaveBeenCalled(); }); }); });

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/devlimelabs/firestore-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server