Skip to main content
Glama

Readwise MCP Server

by IAmAlexander
get-documents.test.ts7.14 kB
import { GetDocumentsTool } from '../../src/tools/get-documents.js'; import { ReadwiseAPI } from '../../src/api/readwise-api.js'; import { Logger } from '../../src/utils/logger.js'; import { Document, PaginatedResponse } from '../../src/types/index.js'; // Mock the ReadwiseAPI jest.mock('../../src/api/readwise-api'); // Get mocked instances of validateNumberRange // This will make our tests match what the implementation actually does jest.mock('../../src/types/validation', () => { const original = jest.requireActual('../../src/types/validation'); return { ...original, validateNumberRange: jest.fn().mockImplementation((params, field, min, max, message) => { // Mock implementation that ensures it handles page: 0 as error and returns consistent objects // Skip validation if field is not present if (params[field] === undefined || params[field] === null) { return { success: true }; } const value = Number(params[field]); if (isNaN(value)) { return { success: false, errors: [{ field, message: message || `${field} must be a number` }] }; } if (min !== undefined && value < min) { return { success: false, errors: [{ field, message: message || `${field} must be at least ${min}` }] }; } if (max !== undefined && value > max) { return { success: false, errors: [{ field, message: message || `${field} must be at most ${max}` }] }; } return { success: true }; }) }; }); describe('GetDocumentsTool', () => { let tool: GetDocumentsTool; let mockApi: jest.Mocked<ReadwiseAPI>; let mockLogger: jest.Mocked<Logger>; beforeEach(() => { // Create mock API mockApi = { getDocuments: jest.fn() } as unknown as jest.Mocked<ReadwiseAPI>; // Create mock logger mockLogger = { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() } as unknown as jest.Mocked<Logger>; // Create the tool tool = new GetDocumentsTool(mockApi, mockLogger); }); afterEach(() => { jest.resetAllMocks(); }); it('should have the correct name and description', () => { expect(tool.name).toBe('get_documents'); expect(tool.description).toBeDefined(); expect(tool.parameters).toBeDefined(); }); it('should validate parameters correctly', () => { const validateNumberRange = jest.requireMock('../../src/types/validation').validateNumberRange; // No mocked validateNumberRange calls should have happened yet expect(validateNumberRange).not.toHaveBeenCalled(); // Test valid parameters - these should pass without calling validateNumberRange expect(tool.validate({})).toEqual(expect.objectContaining({ success: true })); // These should call validateNumberRange but still pass because our mock returns success validateNumberRange.mockReturnValueOnce({ success: true }); expect(tool.validate({ page: 1 })).toEqual(expect.objectContaining({ success: true })); validateNumberRange.mockReturnValueOnce({ success: true }); expect(tool.validate({ page_size: 10 })).toEqual(expect.objectContaining({ success: true })); validateNumberRange.mockReturnValueOnce({ success: true }); validateNumberRange.mockReturnValueOnce({ success: true }); expect(tool.validate({ page: 1, page_size: 10 })).toEqual(expect.objectContaining({ success: true })); // Test invalid parameters - these should fail validateNumberRange.mockReturnValueOnce({ success: false, errors: [{ field: 'page', message: 'Page must be a positive number' }] }); expect(tool.validate({ page: 0 })).toEqual(expect.objectContaining({ success: false })); validateNumberRange.mockReturnValueOnce({ success: false, errors: [{ field: 'page', message: 'Page must be a positive number' }] }); expect(tool.validate({ page: -1 })).toEqual(expect.objectContaining({ success: false })); validateNumberRange.mockReturnValueOnce({ success: false, errors: [{ field: 'page_size', message: 'Page size must be a number between 1 and 100' }] }); expect(tool.validate({ page_size: 0 })).toEqual(expect.objectContaining({ success: false })); validateNumberRange.mockReturnValueOnce({ success: false, errors: [{ field: 'page_size', message: 'Page size must be a number between 1 and 100' }] }); expect(tool.validate({ page_size: -10 })).toEqual(expect.objectContaining({ success: false })); validateNumberRange.mockReturnValueOnce({ success: false, errors: [{ field: 'page_size', message: 'Page size must be a number between 1 and 100' }] }); expect(tool.validate({ page_size: 101 })).toEqual(expect.objectContaining({ success: false })); }); it('should execute and return documents', async () => { // Set up mock response const mockResponse: PaginatedResponse<Document> = { count: 2, next: null, previous: null, results: [ { id: '1', title: 'Test Document 1', author: 'Test Author 1', source: 'article', url: 'https://example.com/article1', created_at: '2023-01-01T12:00:00Z', updated_at: '2023-01-01T12:00:00Z' }, { id: '2', title: 'Test Document 2', author: 'Test Author 2', source: 'pdf', url: 'https://example.com/article2', created_at: '2023-01-02T12:00:00Z', updated_at: '2023-01-02T12:00:00Z' } ] }; mockApi.getDocuments.mockResolvedValue(mockResponse); // Execute the tool const result = await tool.execute({}); // Verify the result expect(result).toEqual({ result: mockResponse }); expect(mockApi.getDocuments).toHaveBeenCalledWith({}); expect(mockLogger.debug).toHaveBeenCalled(); }); it('should handle API errors correctly', async () => { // Set up mock error const apiError = { type: 'api', details: { status: 401, code: 'api_error', message: 'Invalid token' } }; mockApi.getDocuments.mockRejectedValue(apiError); // Execute the tool and expect it to reject await expect(tool.execute({})).rejects.toEqual(apiError); expect(mockLogger.error).toHaveBeenCalled(); }); it('should handle unexpected errors correctly', async () => { // Spy on the error logger mockLogger.error = jest.fn(); // Set up the API mock to throw an unexpected error mockApi.getDocuments.mockImplementation(() => { throw new Error('Unexpected error'); }); // Execute the tool and expect it to not throw but return error in the result const result = await tool.execute({}); expect(result).toEqual({ result: { count: 0, next: null, previous: null, results: [] }, success: false, error: 'An unexpected error occurred while fetching documents' }); expect(mockLogger.error).toHaveBeenCalled(); }); });

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/IAmAlexander/readwise-mcp'

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