Skip to main content
Glama
tool-implementations.test.js39.8 kB
import { describe, it, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert'; // Import specific tool implementations for detailed testing import { discoverTools } from '../lib/tools.js'; describe('Tool Implementation Details', () => { let originalEnv; let originalFetch; let tools; let mockResponses; beforeEach(async () => { // Save original environment and fetch originalEnv = { ...process.env }; originalFetch = global.fetch; // Set test environment variables process.env.WEBEX_PUBLIC_WORKSPACE_API_KEY = 'test-token-123'; process.env.WEBEX_API_BASE_URL = 'https://webexapis.com/v1'; // Load tools tools = await discoverTools(); // Setup mock responses mockResponses = new Map(); // Mock fetch with configurable responses global.fetch = async (url, options) => { const key = `${options.method || 'GET'} ${url}`; if (mockResponses.has(key)) { return mockResponses.get(key); } // Default successful response return { ok: true, status: 200, json: async () => ({ success: true, id: 'test-id' }), text: async () => '{"success":true,"id":"test-id"}' }; }; }); afterEach(() => { // Restore original environment and fetch process.env = originalEnv; global.fetch = originalFetch; }); describe('Message Tools', () => { describe('create_message', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'create_message'); }); it('should handle room messages correctly', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-123', roomId: 'room-123', text: 'Hello' }) }; }; const result = await tool.function({ roomId: 'room-123', text: 'Hello World' }); assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/messages'); assert.strictEqual(capturedRequest.options.method, 'POST'); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.roomId, 'room-123'); assert.strictEqual(body.text, 'Hello World'); }); it('should handle direct messages correctly', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-124', toPersonEmail: 'user@example.com' }) }; }; await tool.function({ toPersonEmail: 'user@example.com', text: 'Direct message' }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.toPersonEmail, 'user@example.com'); assert.strictEqual(body.text, 'Direct message'); }); it('should handle markdown messages', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-125' }) }; }; await tool.function({ roomId: 'room-123', text: 'Plain text', markdown: '**Bold text**' }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.markdown, '**Bold text**'); }); it('should handle file attachments', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-126' }) }; }; await tool.function({ roomId: 'room-123', text: 'Message with file', files: ['https://example.com/file.pdf'] }); const body = JSON.parse(capturedRequest.options.body); assert.deepStrictEqual(body.files, ['https://example.com/file.pdf']); }); it('should use centralized webex config for URL and headers', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-127', roomId: 'room-123', text: 'Test' }) }; }; await tool.function({ roomId: 'room-123', text: 'Test message' }); // Verify URL uses centralized config assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/messages'); assert.strictEqual(capturedRequest.options.method, 'POST'); // Verify headers use centralized config assert.ok(capturedRequest.options.headers.Authorization, 'Should include Authorization header'); assert.ok(capturedRequest.options.headers.Authorization.startsWith('Bearer '), 'Should use Bearer token'); assert.strictEqual(capturedRequest.options.headers['Content-Type'], 'application/json', 'Should set JSON content type'); assert.strictEqual(capturedRequest.options.headers.Accept, 'application/json', 'Should accept JSON'); }); it('should handle conditional payload parameters', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-128' }) }; }; // Test with minimal parameters await tool.function({ roomId: 'room-123', text: 'Simple message' }); let body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.roomId, 'room-123'); assert.strictEqual(body.text, 'Simple message'); assert.ok(!body.parentId, 'Should not include undefined parentId'); assert.ok(!body.markdown, 'Should not include undefined markdown'); assert.ok(!body.files, 'Should not include empty files array'); // Test with all parameters await tool.function({ roomId: 'room-456', parentId: 'parent-123', text: 'Reply message', markdown: '**Bold reply**', files: ['https://example.com/doc.pdf'], attachments: [{ type: 'card' }] }); body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.roomId, 'room-456'); assert.strictEqual(body.parentId, 'parent-123'); assert.strictEqual(body.text, 'Reply message'); assert.strictEqual(body.markdown, '**Bold reply**'); assert.deepStrictEqual(body.files, ['https://example.com/doc.pdf']); assert.deepStrictEqual(body.attachments, [{ type: 'card' }]); }); }); describe('list_messages', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'list_messages'); }); it('should construct query parameters correctly', async () => { let capturedUrl; global.fetch = async (url, options) => { capturedUrl = url; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({ roomId: 'room-123', max: 25, mentionedPeople: 'me' }); const url = new URL(capturedUrl); assert.strictEqual(url.searchParams.get('roomId'), 'room-123'); assert.strictEqual(url.searchParams.get('max'), '25'); assert.strictEqual(url.searchParams.get('mentionedPeople'), 'me'); }); it('should handle optional parameters', async () => { let capturedUrl; global.fetch = async (url, options) => { capturedUrl = url; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({ roomId: 'room-123' }); const url = new URL(capturedUrl); assert.strictEqual(url.searchParams.get('roomId'), 'room-123'); assert.ok(!url.searchParams.has('mentionedPeople')); }); }); describe('get_message_details', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'get_message_details'); }); it('should get message details with correct URL and headers', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-123', text: 'Test message', roomId: 'room-123' }) }; }; await tool.function({ messageId: 'msg-123' }); // Verify URL construction with message ID assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/messages/msg-123'); assert.strictEqual(capturedRequest.options.method, 'GET'); // Verify headers use centralized config assert.ok(capturedRequest.options.headers.Authorization, 'Should include Authorization header'); assert.ok(capturedRequest.options.headers.Authorization.startsWith('Bearer '), 'Should use Bearer token'); assert.strictEqual(capturedRequest.options.headers.Accept, 'application/json', 'Should accept JSON'); }); it('should handle URL encoding for message IDs', async () => { let capturedUrl; global.fetch = async (url, options) => { capturedUrl = url; return { ok: true, status: 200, json: async () => ({ id: 'msg-123', text: 'Test' }) }; }; await tool.function({ messageId: 'msg with spaces' }); assert.ok(capturedUrl.includes('msg%20with%20spaces')); }); it('should handle error responses gracefully', async () => { global.fetch = async () => ({ ok: false, status: 404, statusText: 'Not Found', text: async () => JSON.stringify({ message: 'Message not found' }) }); const result = await tool.function({ messageId: 'nonexistent' }); assert.ok(result.error || result.message, 'Should return error information'); }); }); describe('edit_message', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'edit_message'); }); it('should edit message with correct URL and payload', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-123', text: 'Updated message', roomId: 'room-123' }) }; }; await tool.function({ messageId: 'msg-123', roomId: 'room-123', text: 'Updated message' }); // Verify URL construction assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/messages/msg-123'); assert.strictEqual(capturedRequest.options.method, 'PUT'); // Verify payload const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.roomId, 'room-123'); assert.strictEqual(body.text, 'Updated message'); // Verify headers assert.ok(capturedRequest.options.headers.Authorization, 'Should include Authorization header'); assert.strictEqual(capturedRequest.options.headers['Content-Type'], 'application/json'); }); it('should handle markdown parameter', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'msg-123', markdown: '**Updated**', roomId: 'room-123' }) }; }; await tool.function({ messageId: 'msg-123', roomId: 'room-123', text: 'Updated message', markdown: '**Updated**' }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.markdown, '**Updated**'); }); }); describe('delete_message', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'delete_message'); }); it('should delete message with correct URL and method', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 204 }; }; await tool.function({ messageId: 'msg-123' }); // Verify URL construction assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/messages/msg-123'); assert.strictEqual(capturedRequest.options.method, 'DELETE'); // Verify headers assert.ok(capturedRequest.options.headers.Authorization, 'Should include Authorization header'); assert.strictEqual(capturedRequest.options.headers.Accept, 'application/json'); }); it('should handle URL encoding for message IDs', async () => { let capturedUrl; global.fetch = async (url, options) => { capturedUrl = url; return { ok: true, status: 204 }; }; await tool.function({ messageId: 'msg with spaces' }); assert.ok(capturedUrl.includes('msg%20with%20spaces')); }); it('should return success message on 204 status', async () => { global.fetch = async () => ({ ok: true, status: 204 }); const result = await tool.function({ messageId: 'msg-123' }); assert.ok(result.message && result.message.includes('deleted'), 'Should return success message'); }); }); }); describe('Room Tools', () => { describe('create_room', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'create_room'); }); it('should create room with basic parameters', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'room-123', title: 'Test Room' }) }; }; await tool.function({ title: 'Test Room' }); assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/rooms'); assert.strictEqual(capturedRequest.options.method, 'POST'); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.title, 'Test Room'); }); it('should handle team rooms', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'room-124', teamId: 'team-123' }) }; }; await tool.function({ title: 'Team Room', teamId: 'team-123' }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.teamId, 'team-123'); }); }); describe('get_room_details', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'get_room_details'); }); it('should get room details with correct URL and headers', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'room-123', title: 'Test Room', type: 'group', isLocked: false, teamId: 'team-456' }) }; }; const result = await tool.function({ roomId: 'room-123' }); // Verify URL construction using centralized config assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/rooms/room-123'); assert.strictEqual(capturedRequest.options.method, 'GET'); // Verify headers include authorization assert.ok(capturedRequest.options.headers.Authorization, 'Should include Authorization header'); assert.ok(capturedRequest.options.headers.Authorization.startsWith('Bearer '), 'Should use Bearer token'); assert.strictEqual(capturedRequest.options.headers.Accept, 'application/json', 'Should accept JSON'); // Verify response assert.strictEqual(result.id, 'room-123'); assert.strictEqual(result.title, 'Test Room'); }); it('should handle URL encoding for room IDs', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'room-with-special-chars' }) }; }; await tool.function({ roomId: 'room with spaces & special chars' }); // Verify URL encoding assert.strictEqual( capturedRequest.url, 'https://webexapis.com/v1/rooms/room%20with%20spaces%20%26%20special%20chars' ); }); it('should handle error responses gracefully', async () => { global.fetch = async () => ({ ok: false, status: 404, statusText: 'Not Found', text: async () => JSON.stringify({ message: 'Room not found' }) }); const result = await tool.function({ roomId: 'nonexistent-room' }); assert.ok(result.error, 'Should return error information'); }); }); describe('list_rooms', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'list_rooms'); }); it('should list rooms with teamId filter', async () => { let capturedUrl; global.fetch = async (url, options) => { capturedUrl = url; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({ teamId: 'team-123', max: 50 }); const url = new URL(capturedUrl); assert.strictEqual(url.searchParams.get('teamId'), 'team-123'); assert.strictEqual(url.searchParams.get('max'), '50'); assert.strictEqual(url.searchParams.get('sortBy'), 'id'); }); it('should list rooms without teamId (all accessible rooms)', async () => { let capturedUrl; global.fetch = async (url, options) => { capturedUrl = url; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({ max: 100 }); const url = new URL(capturedUrl); assert.strictEqual(url.searchParams.get('teamId'), null); assert.strictEqual(url.searchParams.get('max'), '100'); }); it('should use type parameter when provided (without orgPublicSpaces)', async () => { let capturedUrl; global.fetch = async (url, options) => { capturedUrl = url; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({ type: 'group', max: 50 }); const url = new URL(capturedUrl); assert.strictEqual(url.searchParams.get('type'), 'group'); assert.strictEqual(url.searchParams.get('orgPublicSpaces'), null); }); it('should use orgPublicSpaces parameter when provided (without type)', async () => { let capturedUrl; global.fetch = async (url, options) => { capturedUrl = url; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({ orgPublicSpaces: true, max: 50 }); const url = new URL(capturedUrl); assert.strictEqual(url.searchParams.get('orgPublicSpaces'), 'true'); assert.strictEqual(url.searchParams.get('type'), null); }); it('should use centralized webex config for URL and headers', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({}); // Verify URL uses centralized config assert.ok(capturedRequest.url.startsWith('https://webexapis.com/v1/rooms')); assert.strictEqual(capturedRequest.options.method, 'GET'); // Verify headers use centralized config assert.ok(capturedRequest.options.headers.Authorization, 'Should include Authorization header'); assert.ok(capturedRequest.options.headers.Authorization.startsWith('Bearer '), 'Should use Bearer token'); assert.strictEqual(capturedRequest.options.headers.Accept, 'application/json', 'Should accept JSON'); }); it('should handle error responses gracefully', async () => { global.fetch = async () => ({ ok: false, status: 403, statusText: 'Forbidden', text: async () => JSON.stringify({ message: 'Access denied' }) }); const result = await tool.function({}); assert.ok(result.error || result.message, 'Should return error information'); }); }); describe('get_room_meeting_details', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'get_room_meeting_details'); }); it('should get room meeting details with correct URL and headers', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ roomId: 'room-123', meetingLink: 'https://cisco.webex.com/m/test-meeting', sipAddress: '123456789@cisco.webex.com', meetingNumber: '123456789' }) }; }; const result = await tool.function({ roomId: 'room-123' }); assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/rooms/room-123/meetingInfo'); assert.strictEqual(capturedRequest.options.method, 'GET'); assert.ok(capturedRequest.options.headers.Authorization, 'Should include Authorization header'); assert.strictEqual(result.roomId, 'room-123'); assert.ok(result.meetingLink, 'Should return meeting link'); }); it('should handle URL encoding for room IDs', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ roomId: 'room-with-special-chars' }) }; }; await tool.function({ roomId: 'room/with/special/chars' }); assert.ok(capturedRequest.url.includes('room%2Fwith%2Fspecial%2Fchars'), 'Should URL encode room ID'); }); }); describe('update_room', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'update_room'); }); it('should update room with correct URL and payload', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'room-123', title: 'Updated Room' }) }; }; await tool.function({ roomId: 'room-123', title: 'Updated Room' }); assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/rooms/room-123'); assert.strictEqual(capturedRequest.options.method, 'PUT'); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.title, 'Updated Room'); }); it('should handle URL encoding for room IDs', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'room-123' }) }; }; await tool.function({ roomId: 'room/with/special/chars', title: 'Test Room' }); assert.ok(capturedRequest.url.includes('room%2Fwith%2Fspecial%2Fchars'), 'Should URL encode room ID'); }); }); describe('delete_room', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'delete_room'); }); it('should delete room with correct URL and handle 204 response', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 204, json: async () => { throw new Error('No content'); } }; }; const result = await tool.function({ roomId: 'room-123' }); assert.strictEqual(capturedRequest.url, 'https://webexapis.com/v1/rooms/room-123'); assert.strictEqual(capturedRequest.options.method, 'DELETE'); assert.ok(result.success, 'Should return success for 204 response'); assert.ok(result.message, 'Should include success message'); }); it('should handle URL encoding for room IDs', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 204 }; }; await tool.function({ roomId: 'room/with/special/chars' }); assert.ok(capturedRequest.url.includes('room%2Fwith%2Fspecial%2Fchars'), 'Should URL encode room ID'); }); it('should handle error responses gracefully', async () => { global.fetch = async () => ({ ok: false, status: 403, statusText: 'Forbidden', text: async () => JSON.stringify({ message: 'Access denied' }) }); const result = await tool.function({ roomId: 'room-123' }); assert.ok(result.error, 'Should return error information'); }); }); }); describe('Membership Tools', () => { describe('create_membership', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'create_membership'); }); it('should create membership with personEmail only', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'membership-123', personEmail: 'test@example.com' }) }; }; await tool.function({ roomId: 'room-123', personEmail: 'test@example.com', isModerator: false }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.roomId, 'room-123'); assert.strictEqual(body.personEmail, 'test@example.com'); assert.strictEqual(body.isModerator, false); assert.strictEqual(body.personId, undefined, 'Should not include personId when using personEmail'); }); it('should create membership with personId only', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'membership-123', personId: 'person-123' }) }; }; await tool.function({ roomId: 'room-123', personId: 'person-123', isModerator: true }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.roomId, 'room-123'); assert.strictEqual(body.personId, 'person-123'); assert.strictEqual(body.isModerator, true); assert.strictEqual(body.personEmail, undefined, 'Should not include personEmail when using personId'); }); }); describe('create_team_membership', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'create_team_membership'); }); it('should create team membership with personEmail only', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'team-membership-123', personEmail: 'test@example.com' }) }; }; await tool.function({ teamId: 'team-123', personEmail: 'test@example.com', isModerator: false }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.teamId, 'team-123'); assert.strictEqual(body.personEmail, 'test@example.com'); assert.strictEqual(body.isModerator, false); assert.strictEqual(body.personId, undefined, 'Should not include personId when using personEmail'); }); it('should create team membership with personId only', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'team-membership-123', personId: 'person-123' }) }; }; await tool.function({ teamId: 'team-123', personId: 'person-123', isModerator: true }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.teamId, 'team-123'); assert.strictEqual(body.personId, 'person-123'); assert.strictEqual(body.isModerator, true); assert.strictEqual(body.personEmail, undefined, 'Should not include personEmail when using personId'); }); }); describe('list_direct_messages', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'list_direct_messages'); }); it('should list direct messages with personEmail only', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({ parentId: 'parent-123', personEmail: 'test@example.com' }); assert.ok(capturedRequest.url.includes('personEmail=test%40example.com'), 'Should include personEmail in URL'); assert.ok(!capturedRequest.url.includes('personId='), 'Should not include personId when using personEmail'); }); it('should list direct messages with personId only', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ items: [] }) }; }; await tool.function({ parentId: 'parent-123', personId: 'person-123' }); assert.ok(capturedRequest.url.includes('personId=person-123'), 'Should include personId in URL'); assert.ok(!capturedRequest.url.includes('personEmail='), 'Should not include personEmail when using personId'); }); }); describe('update_person', () => { let tool; beforeEach(() => { tool = tools.find(t => t.definition.function.name === 'update_person'); }); it('should update person and filter out null avatar', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'person-123', displayName: 'Test User' }) }; }; await tool.function({ personId: 'person-123', personDetails: { displayName: 'Test User', avatar: null, title: 'Developer' } }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.displayName, 'Test User'); assert.strictEqual(body.title, 'Developer'); assert.strictEqual(body.avatar, undefined, 'Should filter out null avatar'); }); it('should update person and filter out undefined avatar', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'person-123', displayName: 'Test User' }) }; }; await tool.function({ personId: 'person-123', personDetails: { displayName: 'Test User', avatar: undefined, title: 'Developer' } }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.displayName, 'Test User'); assert.strictEqual(body.title, 'Developer'); assert.strictEqual(body.avatar, undefined, 'Should filter out undefined avatar'); }); it('should keep valid avatar URL', async () => { let capturedRequest; global.fetch = async (url, options) => { capturedRequest = { url, options }; return { ok: true, status: 200, json: async () => ({ id: 'person-123', displayName: 'Test User' }) }; }; await tool.function({ personId: 'person-123', personDetails: { displayName: 'Test User', avatar: 'https://example.com/avatar.jpg', title: 'Developer' } }); const body = JSON.parse(capturedRequest.options.body); assert.strictEqual(body.displayName, 'Test User'); assert.strictEqual(body.title, 'Developer'); assert.strictEqual(body.avatar, 'https://example.com/avatar.jpg', 'Should keep valid avatar URL'); }); }); }); describe('Error Handling', () => { it('should handle HTTP errors consistently', async () => { const testTools = ['create_message', 'list_rooms', 'get_my_own_details']; for (const toolName of testTools) { const tool = tools.find(t => t.definition.function.name === toolName); if (!tool) continue; global.fetch = async () => ({ ok: false, status: 404, statusText: 'Not Found', json: async () => ({ message: 'Resource not found' }) }); const params = toolName === 'create_message' ? { roomId: 'room-123', text: 'test' } : toolName === 'list_rooms' ? {} : {}; const result = await tool.function(params); // Should handle error gracefully assert.ok(result, `${toolName} should return result even on error`); } }); it('should handle network timeouts', async () => { const tool = tools.find(t => t.definition.function.name === 'get_my_own_details'); global.fetch = async () => { throw new Error('Network timeout'); }; const result = await tool.function({}); // Should handle timeout gracefully assert.ok(result, 'Should handle network errors'); }); it('should handle malformed JSON responses', async () => { const tool = tools.find(t => t.definition.function.name === 'list_messages'); global.fetch = async () => ({ ok: true, status: 200, json: async () => { throw new Error('Invalid JSON'); }, text: async () => 'invalid json response' }); const result = await tool.function({ roomId: 'room-123' }); // Should handle JSON parsing errors assert.ok(result, 'Should handle malformed JSON'); }); }); describe('Authentication', () => { it('should handle authentication errors', async () => { const tool = tools.find(t => t.definition.function.name === 'create_message'); global.fetch = async () => ({ ok: false, status: 401, statusText: 'Unauthorized', json: async () => ({ message: 'The request requires a valid access token.', errors: [{ description: 'The request requires a valid access token.' }] }) }); const result = await tool.function({ roomId: 'room-123', text: 'test message' }); // Should return error information assert.ok(result.error || result.message, 'Should indicate authentication error'); }); it('should include proper authorization headers', async () => { const testTools = ['create_message', 'list_rooms', 'create_webhook']; for (const toolName of testTools) { const tool = tools.find(t => t.definition.function.name === toolName); if (!tool) continue; let capturedHeaders; global.fetch = async (url, options) => { capturedHeaders = options.headers; return { ok: true, status: 200, json: async () => ({}) }; }; const params = toolName === 'create_message' ? { roomId: 'room-123', text: 'test' } : toolName === 'create_webhook' ? { name: 'test', targetUrl: 'https://example.com', resource: 'messages', event: 'created' } : {}; await tool.function(params); assert.ok(capturedHeaders.Authorization, `${toolName} should include Authorization header`); assert.ok(capturedHeaders.Authorization.startsWith('Bearer '), `${toolName} should use Bearer token`); } }); }); });

Latest Blog Posts

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/Kashyap-AI-ML-Solutions/webex-messaging-mcp-server'

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