UNPKG

dbx-mcp-server

Version:

A Model Context Protocol server for Dropbox integration with AI-powered PDF analysis, multi-directory indexing, and parallel processing

222 lines 9.56 kB
import { describe, test, expect, beforeAll, jest } from '@jest/globals'; import { analyzePDF } from '../src/pdf-analyzer.js'; import { indexFile, searchIndexed, getDocumentStats } from '../src/document-indexer.js'; // Mock the dbx-api module jest.mock('../src/dbx-api.js', () => ({ getRawFileMetadata: jest.fn(), downloadFileAsBuffer: jest.fn(), searchFiles: jest.fn(), })); // Mock the config module jest.mock('../src/config.js', () => ({ config: { dropbox: { appKey: 'test-key', appSecret: 'test-secret', redirectUri: 'http://localhost:3000/callback' } }, log: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), } })); // Mock the ai-parser module jest.mock('../src/ai-parser.js', () => ({ aiParser: { parseWithRetry: jest.fn(), } })); // Mock the database module jest.mock('../src/database.js', () => ({ documentDb: { getDocumentByDropboxPath: jest.fn(), insertDocument: jest.fn(), searchDocuments: jest.fn(), getDocumentStats: jest.fn(), getExpiringContracts: jest.fn(), getOverdueInvoices: jest.fn(), insertInvoice: jest.fn(), insertContract: jest.fn(), insertProposal: jest.fn(), insertReport: jest.fn(), insertInvoiceItems: jest.fn(), insertContractParties: jest.fn(), } })); describe('PDF Analysis Tools', () => { beforeAll(() => { // Set up environment variables for tests process.env.OPENROUTER_API_KEY = 'test-api-key'; }); describe('analyzePDF', () => { test('should reject non-PDF files', async () => { const { getRawFileMetadata } = await import('../src/dbx-api.js'); const mockGetMetadata = getRawFileMetadata; mockGetMetadata.mockResolvedValue({ name: 'document.txt', size: 1000, }); const result = await analyzePDF({ path: '/test/document.txt', prompt: 'Analyze this document' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('not a PDF file'); }); test('should reject files larger than 20MB', async () => { const { getRawFileMetadata } = await import('../src/dbx-api.js'); const mockGetMetadata = getRawFileMetadata; mockGetMetadata.mockResolvedValue({ name: 'large-document.pdf', size: 25 * 1024 * 1024, // 25MB }); const result = await analyzePDF({ path: '/test/large-document.pdf', prompt: 'Analyze this document' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('exceeds 20MB limit'); }); test('should handle valid PDF analysis request', async () => { const { getRawFileMetadata, downloadFileAsBuffer } = await import('../src/dbx-api.js'); const mockGetMetadata = getRawFileMetadata; const mockDownloadBuffer = downloadFileAsBuffer; mockGetMetadata.mockResolvedValue({ name: 'test-invoice.pdf', size: 1024 * 1024, // 1MB }); mockDownloadBuffer.mockResolvedValue(Buffer.from('mock-pdf-content')); // Mock fetch for OpenRouter API global.fetch = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ choices: [{ message: { content: 'This is a test invoice document analysis.' } }], usage: { total_tokens: 500 } }) }); const result = await analyzePDF({ path: '/test/test-invoice.pdf', prompt: 'What type of document is this?' }); expect(result.isError).toBe(false); expect(result.content[0].text).toContain('Analysis of "test-invoice.pdf"'); expect(result.content[0].text).toContain('This is a test invoice document analysis.'); }); }); describe('indexFile', () => { test('should reject non-PDF files for indexing', async () => { const { getRawFileMetadata } = await import('../src/dbx-api.js'); const mockGetMetadata = getRawFileMetadata; mockGetMetadata.mockResolvedValue({ name: 'document.txt', }); const result = await indexFile({ path: '/test/document.txt' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Only PDF files can be indexed'); }); test('should skip already indexed and up-to-date files', async () => { const { getRawFileMetadata } = await import('../src/dbx-api.js'); const { documentDb } = await import('../src/database.js'); const mockGetMetadata = getRawFileMetadata; const mockGetDocument = documentDb.getDocumentByDropboxPath; mockGetMetadata.mockResolvedValue({ name: 'test-invoice.pdf', client_modified: '2024-01-01T00:00:00Z', }); mockGetDocument.mockResolvedValue({ id: 1, dropbox_path: '/test/test-invoice.pdf', name: 'test-invoice.pdf', mime_type: 'application/pdf', size: 1000, created_time: '2024-01-01T00:00:00Z', modified_time: '2024-01-01T00:00:00Z', ai_model: 'gemini-2.5-flash-preview-05-20', doc_type: 'invoice', synopsis: 'Test invoice', confidence_score: 0.9, indexed_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }); const result = await indexFile({ path: '/test/test-invoice.pdf' }); expect(result.isError).toBe(false); expect(result.content[0].text).toContain('already up-to-date in index'); }); }); describe('searchIndexed', () => { test('should return empty results when no documents match', async () => { const { documentDb } = await import('../src/database.js'); const mockSearchDocuments = documentDb.searchDocuments; mockSearchDocuments.mockResolvedValue([]); const result = await searchIndexed({ query: 'nonexistent document' }); expect(result.isError).toBe(false); expect(result.content[0].text).toContain('No indexed documents found matching'); }); test('should return formatted search results', async () => { const { documentDb } = await import('../src/database.js'); const mockSearchDocuments = documentDb.searchDocuments; mockSearchDocuments.mockResolvedValue([ { id: 1, dropbox_path: '/test/invoice.pdf', name: 'invoice.pdf', mime_type: 'application/pdf', size: 1000, created_time: '2024-01-01T00:00:00Z', modified_time: '2024-01-01T00:00:00Z', ai_model: 'gemini-2.5-flash-preview-05-20', doc_type: 'invoice', synopsis: 'Test invoice for services', confidence_score: 0.95, indexed_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', } ]); const result = await searchIndexed({ query: 'invoice' }); expect(result.isError).toBe(false); expect(result.content[0].text).toContain('Found 1 indexed document(s)'); expect(result.content[0].text).toContain('invoice.pdf'); expect(result.content[0].text).toContain('Test invoice for services'); expect(result.content[0].text).toContain('95.0%'); }); }); describe('getDocumentStats', () => { test('should return formatted document statistics', async () => { const { documentDb } = await import('../src/database.js'); const mockGetStats = documentDb.getDocumentStats; const mockGetExpiring = documentDb.getExpiringContracts; const mockGetOverdue = documentDb.getOverdueInvoices; mockGetStats.mockResolvedValue({ total_documents: 5, by_type: [ { doc_type: 'invoice', count: 3, avg_confidence: 0.9 }, { doc_type: 'contract', count: 2, avg_confidence: 0.85 } ] }); mockGetExpiring.mockResolvedValue([]); mockGetOverdue.mockResolvedValue([]); const result = await getDocumentStats(); expect(result.isError).toBe(false); expect(result.content[0].text).toContain('Total Documents:** 5'); expect(result.content[0].text).toContain('invoice: 3 (avg confidence: 90.0%)'); expect(result.content[0].text).toContain('contract: 2 (avg confidence: 85.0%)'); }); }); }); //# sourceMappingURL=pdf-analysis.test.js.map