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
JavaScript
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