Skip to main content
Glama

Twilio MCP Server (Docker Edition with Auth Token Support)

by Twine2546
http.spec.ts•19.3 kB
import fetch, { Response } from 'node-fetch'; import { beforeEach, describe, expect, it, Mock, test, vi } from 'vitest'; import FormData from 'form-data'; import Http, { Authorization, interpolateUrl } from '@app/utils/http'; // Mock node-fetch vi.mock('node-fetch', () => ({ default: vi.fn(), })); describe('Http', () => { const mockCredentials: Authorization = { type: 'Basic', username: 'test-username', password: 'test-password', }; const mockResponse = (status: number, jsonData: any, ok = true): Response => { return { ok, status, json: vi.fn().mockResolvedValue(jsonData), text: vi.fn().mockResolvedValue(JSON.stringify(jsonData)), } as unknown as Response; }; let http: Http; beforeEach(() => { http = new Http({ authorization: mockCredentials }); vi.clearAllMocks(); }); describe('GET requests', () => { it('should make a successful GET request', async () => { const mockData = { data: 'test data' }; const mockResponseObj = mockResponse(200, mockData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.get('https://api.example.com/test'); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/test', expect.objectContaining({ method: 'GET', headers: expect.objectContaining({ Authorization: 'Basic dGVzdC11c2VybmFtZTp0ZXN0LXBhc3N3b3Jk', 'Content-Type': 'application/json', 'x-mcp-server-id': expect.stringMatching( 'twilio-openapi-mcp-server', ), 'user-agent': expect.stringMatching('twilio-openapi-mcp-server'), }), }), ); expect(result).toEqual({ ok: true, statusCode: 200, data: mockData, response: mockResponseObj, }); }); it('should handle a failed GET request with error response', async () => { const errorData = { error: 'Not found' }; const mockResponseObj = mockResponse(404, errorData, false); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.get('https://api.example.com/notfound'); expect(result).toEqual({ ok: false, statusCode: 404, error: new Error(JSON.stringify(errorData)), response: mockResponseObj, }); }); it('should handle network errors during GET requests', async () => { (fetch as unknown as Mock).mockRejectedValueOnce( new Error('Network error'), ); const result = await http.get('https://api.example.com/test'); expect(result).toEqual({ ok: false, statusCode: 500, error: new Error('An error occurred while making the request'), }); }); }); describe('POST requests', () => { it('should make a successful POST request with JSON body', async () => { const requestBody = { name: 'Test Name', value: 123 }; const responseData = { id: '12345', ...requestBody }; const mockResponseObj = mockResponse(201, responseData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.post( 'https://api.example.com/create', requestBody, ); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/create', expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ 'Content-Type': 'application/json', }), body: JSON.stringify(requestBody), }), ); expect(result).toEqual({ ok: true, statusCode: 201, data: responseData, response: mockResponseObj, }); }); it('should make a POST request with urlencoded body', async () => { const requestBody = { name: 'Test Name', value: 123 }; const responseData = { id: '12345', success: true }; const mockResponseObj = mockResponse(200, responseData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.post( 'https://api.example.com/form', requestBody, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }, ); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/form', expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ 'Content-Type': 'application/x-www-form-urlencoded', 'x-mcp-server-id': expect.stringMatching( 'twilio-openapi-mcp-server', ), 'user-agent': expect.stringMatching('twilio-openapi-mcp-server'), }), body: expect.any(String), // We're not testing the exact encoding, just that it's a string }), ); expect(result).toEqual({ ok: true, statusCode: 200, data: responseData, response: mockResponseObj, }); }); it('should handle failed POST requests', async () => { const requestBody = { invalid: 'data' }; const errorData = { error: 'Invalid request' }; const mockResponseObj = mockResponse(400, errorData, false); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.post( 'https://api.example.com/create', requestBody, ); expect(result).toEqual({ ok: false, statusCode: 400, error: new Error(JSON.stringify(errorData)), response: mockResponseObj, }); }); }); describe('PUT requests', () => { it('should make a successful PUT request with JSON body', async () => { const requestBody = { id: '12345', name: 'Updated Name' }; const responseData = { ...requestBody, updated: true }; const mockResponseObj = mockResponse(200, responseData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.put( 'https://api.example.com/update/12345', requestBody, ); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/update/12345', expect.objectContaining({ method: 'PUT', headers: expect.objectContaining({ Authorization: 'Basic dGVzdC11c2VybmFtZTp0ZXN0LXBhc3N3b3Jk', 'Content-Type': 'application/json', 'x-mcp-server-id': expect.stringMatching( 'twilio-openapi-mcp-server', ), 'user-agent': expect.stringMatching('twilio-openapi-mcp-server'), }), body: JSON.stringify(requestBody), }), ); expect(result).toEqual({ ok: true, statusCode: 200, data: responseData, response: mockResponseObj, }); }); it('should make a PUT request with urlencoded body', async () => { const requestBody = { id: '12345', name: 'Updated Name' }; const responseData = { ...requestBody, updated: true }; const mockResponseObj = mockResponse(200, responseData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.put( 'https://api.example.com/update/12345', requestBody, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, }, ); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/update/12345', expect.objectContaining({ method: 'PUT', headers: expect.objectContaining({ 'Content-Type': 'application/x-www-form-urlencoded', 'x-mcp-server-id': expect.stringMatching( 'twilio-openapi-mcp-server', ), 'user-agent': expect.stringMatching('twilio-openapi-mcp-server'), }), body: expect.any(String), }), ); expect(result).toEqual({ ok: true, statusCode: 200, data: responseData, response: mockResponseObj, }); }); it('should handle failed PUT requests', async () => { const requestBody = { id: 'nonexistent', name: 'Updated Name' }; const errorData = { error: 'Resource not found' }; const mockResponseObj = mockResponse(404, errorData, false); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.put( 'https://api.example.com/update/nonexistent', requestBody, ); expect(result).toEqual({ ok: false, statusCode: 404, error: new Error(JSON.stringify(errorData)), response: mockResponseObj, }); }); }); describe('DELETE requests', () => { it('should make a successful DELETE request', async () => { const responseData = { deleted: true, id: '12345' }; const mockResponseObj = mockResponse(200, responseData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.delete('https://api.example.com/delete/12345'); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/delete/12345', expect.objectContaining({ method: 'DELETE', headers: expect.objectContaining({ 'Content-Type': 'application/json', 'x-mcp-server-id': expect.stringMatching( 'twilio-openapi-mcp-server', ), 'user-agent': expect.stringMatching('twilio-openapi-mcp-server'), }), }), ); expect(result).toEqual({ ok: true, statusCode: 200, data: responseData, response: mockResponseObj, }); }); it('should handle failed DELETE requests', async () => { const errorData = { error: 'Forbidden' }; const mockResponseObj = mockResponse(403, errorData, false); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.delete( 'https://api.example.com/delete/protected', ); expect(result).toEqual({ ok: false, statusCode: 403, error: new Error(JSON.stringify(errorData)), response: mockResponseObj, }); }); }); describe('request with custom headers', () => { it('should merge custom headers with default headers', async () => { const mockData = { data: 'test data' }; const mockResponseObj = mockResponse(200, mockData); const customHeaders = { 'X-Custom-Header': 'custom-value' }; const httpWithCustomHeaders = new Http({ authorization: mockCredentials, }); // @ts-ignore - accessing private method for testing const makeRequest = vi.spyOn(httpWithCustomHeaders, 'make'); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); await httpWithCustomHeaders.get('https://api.example.com/test'); // We're checking if the make method includes the expected headers expect(makeRequest).toHaveBeenCalledWith({ url: 'https://api.example.com/test', method: 'GET', }); // Now test with custom headers by modifying the internal request // This is a bit of a hack but necessary to test header merging (fetch as unknown as Mock).mockImplementationOnce((url, options) => { // Check if headers were properly merged expect(options.headers).toHaveProperty('Authorization'); expect(options.headers).toHaveProperty( 'Content-Type', 'application/json', ); return Promise.resolve(mockResponseObj); }); // @ts-ignore - accessing private method for testing await httpWithCustomHeaders.make({ url: 'https://api.example.com/test', method: 'GET', headers: customHeaders, }); }); }); describe('interpolateUrl', () => { test('should return the original URL if no params are provided', () => { expect(interpolateUrl('/api/v1/resource')).toBe('/api/v1/resource'); expect(interpolateUrl('/api/v1/resource', undefined)).toBe( '/api/v1/resource', ); }); test('should return the original URL if params is an array', () => { expect(interpolateUrl('/api/v1/resource', [] as any)).toBe( '/api/v1/resource', ); }); test('should replace string parameters in the URL', () => { expect(interpolateUrl('/api/{version}/resource', { version: 'v1' })).toBe( '/api/v1/resource', ); expect( interpolateUrl('/api/{version}/resource/{id}', { version: 'v1', id: 'abc123', }), ).toBe('/api/v1/resource/abc123'); }); test('should replace number parameters in the URL', () => { expect(interpolateUrl('/api/v1/resource/{id}', { id: 123 })).toBe( '/api/v1/resource/123', ); }); test('should replace boolean parameters in the URL', () => { expect( interpolateUrl('/api/v1/resource/{active}', { active: true }), ).toBe('/api/v1/resource/true'); expect( interpolateUrl('/api/v1/resource/{active}', { active: false }), ).toBe('/api/v1/resource/false'); }); test('should not replace parameters of unsupported types', () => { expect( interpolateUrl('/api/v1/resource/{obj}', { obj: { key: 'value' } }), ).toBe('/api/v1/resource/{obj}'); expect(interpolateUrl('/api/v1/resource/{arr}', { arr: [1, 2, 3] })).toBe( '/api/v1/resource/{arr}', ); expect(interpolateUrl('/api/v1/resource/{nul}', { nul: null })).toBe( '/api/v1/resource/{nul}', ); }); test('should handle multiple replacements of different types', () => { expect( interpolateUrl('/api/{version}/resource/{id}/status/{active}', { version: 'v1', id: 123, active: true, }), ).toBe('/api/v1/resource/123/status/true'); }); test('should leave placeholders for missing parameters', () => { expect( interpolateUrl('/api/{version}/resource/{id}', { version: 'v1' }), ).toBe('/api/v1/resource/{id}'); }); }); describe('upload requests', () => { it('should make a successful file upload request', async () => { const formData = new FormData(); formData.append('file', 'test file content', 'test.txt'); formData.append('description', 'Test file upload'); // Mock FormData's getHeaders method formData.getHeaders = vi.fn().mockReturnValue({ 'content-type': 'multipart/form-data; boundary=---boundary', }); const responseData = { fileId: '12345', success: true }; const mockResponseObj = mockResponse(201, responseData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.upload( 'https://api.example.com/upload', formData, ); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/upload', expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ 'content-type': 'multipart/form-data; boundary=---boundary', Authorization: 'Basic dGVzdC11c2VybmFtZTp0ZXN0LXBhc3N3b3Jk', 'x-mcp-server-id': expect.stringMatching( 'twilio-openapi-mcp-server', ), 'user-agent': expect.stringMatching('twilio-openapi-mcp-server'), }), body: formData, }), ); expect(result).toEqual({ ok: true, statusCode: 201, data: responseData, response: mockResponseObj, }); }); it('should handle failed upload requests', async () => { const formData = new FormData(); formData.append('file', 'invalid file content', 'invalid.txt'); // Mock FormData's getHeaders method formData.getHeaders = vi.fn().mockReturnValue({ 'content-type': 'multipart/form-data; boundary=---boundary', }); const errorData = { error: 'File upload failed' }; const mockResponseObj = mockResponse(400, errorData, false); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.upload( 'https://api.example.com/upload', formData, ); expect(result).toEqual({ ok: false, statusCode: 400, error: new Error(JSON.stringify(errorData)), response: mockResponseObj, }); }); it('should upload with custom headers', async () => { const formData = new FormData(); formData.append('file', 'test content', 'test.txt'); // Mock FormData's getHeaders method formData.getHeaders = vi.fn().mockReturnValue({ 'content-type': 'multipart/form-data; boundary=---boundary', }); const responseData = { fileId: '12345', success: true }; const mockResponseObj = mockResponse(200, responseData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.upload( 'https://api.example.com/upload', formData, { headers: { 'X-Custom-Header': 'custom-value' } }, ); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/upload', expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ 'content-type': 'multipart/form-data; boundary=---boundary', 'X-Custom-Header': 'custom-value', Authorization: 'Basic dGVzdC11c2VybmFtZTp0ZXN0LXBhc3N3b3Jk', 'x-mcp-server-id': expect.stringMatching( 'twilio-openapi-mcp-server', ), 'user-agent': expect.stringMatching('twilio-openapi-mcp-server'), }), body: formData, }), ); expect(result).toEqual({ ok: true, statusCode: 200, data: responseData, response: mockResponseObj, }); }); it('should handle FormData without getHeaders method', async () => { const formData = new FormData(); formData.append('file', 'test content', 'test.txt'); // Simulate FormData without getHeaders method formData.getHeaders = undefined; const responseData = { fileId: '12345', success: true }; const mockResponseObj = mockResponse(200, responseData); (fetch as unknown as Mock).mockResolvedValueOnce(mockResponseObj); const result = await http.upload( 'https://api.example.com/upload', formData, ); expect(fetch).toHaveBeenCalledWith( 'https://api.example.com/upload', expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ Authorization: 'Basic dGVzdC11c2VybmFtZTp0ZXN0LXBhc3N3b3Jk', 'x-mcp-server-id': expect.stringMatching( 'twilio-openapi-mcp-server', ), 'user-agent': expect.stringMatching('twilio-openapi-mcp-server'), }), body: formData, }), ); expect(result).toEqual({ ok: true, statusCode: 200, data: responseData, response: mockResponseObj, }); }); }); });

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/Twine2546/twilio-mcp-docker'

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