Skip to main content
Glama

Notion ReadOnly MCP Server

by Taewoong1378
http-client.test.ts14.9 kB
import { HttpClient, HttpClientError } from '../http-client' import { OpenAPIV3 } from 'openapi-types' import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import OpenAPIClientAxios from 'openapi-client-axios' // Mock the OpenAPIClientAxios initialization vi.mock('openapi-client-axios', () => { const mockApi = { getPet: vi.fn(), testOperation: vi.fn(), complexOperation: vi.fn(), } return { default: vi.fn().mockImplementation(() => ({ init: vi.fn().mockResolvedValue(mockApi), })), } }) describe('HttpClient', () => { let client: HttpClient let mockApi: any const sampleSpec: OpenAPIV3.Document = { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, paths: { '/pets/{petId}': { get: { operationId: 'getPet', parameters: [ { name: 'petId', in: 'path', required: true, schema: { type: 'integer' }, }, ], responses: { '200': { description: 'OK', content: { 'application/json': { schema: { type: 'object' }, }, }, }, }, }, }, }, } const getPetOperation = sampleSpec.paths['/pets/{petId}']?.get as OpenAPIV3.OperationObject & { method: string; path: string } if (!getPetOperation) { throw new Error('Test setup error: getPet operation not found in sample spec') } beforeEach(async () => { // Create a new instance of HttpClient client = new HttpClient({ baseUrl: 'https://api.example.com' }, sampleSpec) // Await the initialization to ensure mockApi is set correctly mockApi = await client['api'] }) afterEach(() => { vi.clearAllMocks() }) it('successfully executes an operation', async () => { const mockResponse = { data: { id: 1, name: 'Fluffy' }, status: 200, headers: { 'content-type': 'application/json', }, } mockApi.getPet.mockResolvedValueOnce(mockResponse) const response = await client.executeOperation(getPetOperation, { petId: 1 }) // Note GET requests should have a null Content-Type header! expect(mockApi.getPet).toHaveBeenCalledWith({ petId: 1 }, undefined, { headers: { 'Content-Type': null } }) expect(response.data).toEqual(mockResponse.data) expect(response.status).toBe(200) expect(response.headers).toBeInstanceOf(Headers) expect(response.headers.get('content-type')).toBe('application/json') }) it('throws error when operation ID is missing', async () => { const operationWithoutId: OpenAPIV3.OperationObject & { method: string; path: string } = { method: 'GET', path: '/unknown', responses: { '200': { description: 'OK', }, }, } await expect(client.executeOperation(operationWithoutId)).rejects.toThrow('Operation ID is required') }) it('throws error when operation is not found', async () => { const operation: OpenAPIV3.OperationObject & { method: string; path: string } = { method: 'GET', path: '/unknown', operationId: 'nonexistentOperation', responses: { '200': { description: 'OK', }, }, } await expect(client.executeOperation(operation)).rejects.toThrow('Operation nonexistentOperation not found') }) it('handles API errors correctly', async () => { const error = { response: { status: 404, statusText: 'Not Found', data: { code: 'RESOURCE_NOT_FOUND', message: 'Pet not found', petId: 999, }, headers: { 'content-type': 'application/json', }, }, } mockApi.getPet.mockRejectedValueOnce(error) await expect(client.executeOperation(getPetOperation, { petId: 999 })).rejects.toMatchObject({ status: 404, message: '404 Not Found', data: { code: 'RESOURCE_NOT_FOUND', message: 'Pet not found', petId: 999, }, }) }) it('handles validation errors (400) correctly', async () => { const error = { response: { status: 400, statusText: 'Bad Request', data: { code: 'VALIDATION_ERROR', message: 'Invalid input data', errors: [ { field: 'age', message: 'Age must be a positive number', }, { field: 'name', message: 'Name is required', }, ], }, headers: { 'content-type': 'application/json', }, }, } mockApi.getPet.mockRejectedValueOnce(error) await expect(client.executeOperation(getPetOperation, { petId: 1 })).rejects.toMatchObject({ status: 400, message: '400 Bad Request', data: { code: 'VALIDATION_ERROR', message: 'Invalid input data', errors: [ { field: 'age', message: 'Age must be a positive number', }, { field: 'name', message: 'Name is required', }, ], }, }) }) it('handles server errors (500) with HTML response', async () => { const error = { response: { status: 500, statusText: 'Internal Server Error', data: '<html><body><h1>500 Internal Server Error</h1></body></html>', headers: { 'content-type': 'text/html', }, }, } mockApi.getPet.mockRejectedValueOnce(error) await expect(client.executeOperation(getPetOperation, { petId: 1 })).rejects.toMatchObject({ status: 500, message: '500 Internal Server Error', data: '<html><body><h1>500 Internal Server Error</h1></body></html>', }) }) it('handles rate limit errors (429)', async () => { const error = { response: { status: 429, statusText: 'Too Many Requests', data: { code: 'RATE_LIMIT_EXCEEDED', message: 'Rate limit exceeded', retryAfter: 60, }, headers: { 'content-type': 'application/json', 'retry-after': '60', }, }, } mockApi.getPet.mockRejectedValueOnce(error) await expect(client.executeOperation(getPetOperation, { petId: 1 })).rejects.toMatchObject({ status: 429, message: '429 Too Many Requests', data: { code: 'RATE_LIMIT_EXCEEDED', message: 'Rate limit exceeded', retryAfter: 60, }, }) }) it('should send body parameters in request body for POST operations', async () => { // Setup mock API with the new operation mockApi.testOperation = vi.fn().mockResolvedValue({ data: {}, status: 200, headers: {}, }) const testSpec: OpenAPIV3.Document = { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, paths: { '/test': { post: { operationId: 'testOperation', requestBody: { content: { 'application/json': { schema: { type: 'object', properties: { foo: { type: 'string' }, }, }, }, }, }, responses: { '200': { description: 'Success response', content: { 'application/json': { schema: { type: 'object', }, }, }, }, }, }, }, }, } const postOperation = testSpec.paths['/test']?.post as OpenAPIV3.OperationObject & { method: string; path: string } if (!postOperation) { throw new Error('Test setup error: post operation not found') } const client = new HttpClient({ baseUrl: 'http://test.com' }, testSpec) await client.executeOperation(postOperation, { foo: 'bar' }) expect(mockApi.testOperation).toHaveBeenCalledWith({}, { foo: 'bar' }, { headers: { 'Content-Type': 'application/json' } }) }) it('should handle query, path, and body parameters correctly', async () => { mockApi.complexOperation = vi.fn().mockResolvedValue({ data: { success: true }, status: 200, headers: { 'content-type': 'application/json', }, }) const complexSpec: OpenAPIV3.Document = { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, paths: { '/users/{userId}/posts': { post: { operationId: 'complexOperation', parameters: [ { name: 'userId', in: 'path', required: true, schema: { type: 'integer' }, }, { name: 'include', in: 'query', required: false, schema: { type: 'string' }, }, ], requestBody: { content: { 'application/json': { schema: { type: 'object', properties: { title: { type: 'string' }, content: { type: 'string' }, }, }, }, }, }, responses: { '200': { description: 'Success response', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, }, }, }, }, }, }, }, }, }, } const complexOperation = complexSpec.paths['/users/{userId}/posts']?.post as OpenAPIV3.OperationObject & { method: string path: string } if (!complexOperation) { throw new Error('Test setup error: complex operation not found') } const client = new HttpClient({ baseUrl: 'http://test.com' }, complexSpec) await client.executeOperation(complexOperation, { // Path parameter userId: 123, // Query parameter include: 'comments', // Body parameters title: 'Test Post', content: 'Test Content', }) expect(mockApi.complexOperation).toHaveBeenCalledWith( { userId: 123, include: 'comments', }, { title: 'Test Post', content: 'Test Content', }, { headers: { 'Content-Type': 'application/json' } }, ) }) const mockOpenApiSpec: OpenAPIV3.Document = { openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' }, paths: { '/test': { post: { operationId: 'testOperation', parameters: [ { name: 'queryParam', in: 'query', schema: { type: 'string' }, }, { name: 'pathParam', in: 'path', schema: { type: 'string' }, }, ], requestBody: { content: { 'application/json': { schema: { type: 'object', properties: { bodyParam: { type: 'string' }, }, }, }, }, }, responses: { '200': { description: 'Success', }, '400': { description: 'Bad Request', }, }, }, }, }, } const mockConfig = { baseUrl: 'http://test-api.com', } beforeEach(() => { vi.clearAllMocks() }) it('should properly propagate structured error responses', async () => { const errorResponse = { response: { data: { code: 'VALIDATION_ERROR', message: 'Invalid input', details: ['Field x is required'], }, status: 400, statusText: 'Bad Request', headers: { 'content-type': 'application/json', }, }, } // Mock axios instance const mockAxiosInstance = { testOperation: vi.fn().mockRejectedValue(errorResponse), } // Mock the OpenAPIClientAxios initialization const MockOpenAPIClientAxios = vi.fn().mockImplementation(() => ({ init: () => Promise.resolve(mockAxiosInstance), })) vi.mocked(OpenAPIClientAxios).mockImplementation(() => MockOpenAPIClientAxios()) const client = new HttpClient(mockConfig, mockOpenApiSpec) const operation = mockOpenApiSpec.paths['/test']?.post if (!operation) { throw new Error('Operation not found in mock spec') } try { await client.executeOperation(operation as OpenAPIV3.OperationObject & { method: string; path: string }, {}) // Should not reach here expect(true).toBe(false) } catch (error: any) { expect(error.status).toBe(400) expect(error.data).toEqual({ code: 'VALIDATION_ERROR', message: 'Invalid input', details: ['Field x is required'], }) expect(error.message).toBe('400 Bad Request') } }) it('should handle query, path, and body parameters correctly', async () => { const mockAxiosInstance = { testOperation: vi.fn().mockResolvedValue({ data: { success: true }, status: 200, headers: { 'content-type': 'application/json' }, }), } const MockOpenAPIClientAxios = vi.fn().mockImplementation(() => ({ init: () => Promise.resolve(mockAxiosInstance), })) vi.mocked(OpenAPIClientAxios).mockImplementation(() => MockOpenAPIClientAxios()) const client = new HttpClient(mockConfig, mockOpenApiSpec) const operation = mockOpenApiSpec.paths['/test']?.post if (!operation) { throw new Error('Operation not found in mock spec') } const response = await client.executeOperation(operation as OpenAPIV3.OperationObject & { method: string; path: string }, { queryParam: 'query1', pathParam: 'path1', bodyParam: 'body1', }) expect(mockAxiosInstance.testOperation).toHaveBeenCalledWith( { queryParam: 'query1', pathParam: 'path1', }, { bodyParam: 'body1', }, { headers: { 'Content-Type': 'application/json' } }, ) // Additional check to ensure headers are correctly processed expect(response.headers.get('content-type')).toBe('application/json') }) })

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/Taewoong1378/notion-readonly-mcp-server'

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