@the_cfdude/productboard-mcp
Version:
Model Context Protocol server for Productboard REST API with dynamic tool loading
140 lines (122 loc) • 4.62 kB
text/typescript
/**
* Tests for Features, Components, and Products tools
*/
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import {
handleFeaturesTool,
setupFeaturesTools,
} from '../../tools/features.js';
import type { ToolDefinition } from '../../types/tool-types.js';
describe('Features Tools', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Tool Definitions', () => {
it('should define all features tools with correct schemas', () => {
const tools = setupFeaturesTools();
expect(tools.length).toBe(6);
const toolNames = tools.map((tool: ToolDefinition) => tool.name);
// Features tools
expect(toolNames).toContain('create_feature');
expect(toolNames).toContain('get_features');
expect(toolNames).toContain('get_feature');
expect(toolNames).toContain('update_feature');
expect(toolNames).toContain('delete_feature');
expect(toolNames).toContain('get_available_fields');
});
it('should have standardized parameters for list operations', () => {
const tools = setupFeaturesTools();
const getFeatures = tools.find(
(t: ToolDefinition) => t.name === 'get_features'
);
expect(getFeatures?.inputSchema.properties).toHaveProperty('limit');
expect(getFeatures?.inputSchema.properties).toHaveProperty('startWith');
expect(getFeatures?.inputSchema.properties).toHaveProperty('detail');
expect(getFeatures?.inputSchema.properties).toHaveProperty(
'includeSubData'
);
});
it('should have standardized parameters for get operations', () => {
const tools = setupFeaturesTools();
const getFeature = tools.find(
(t: ToolDefinition) => t.name === 'get_feature'
);
expect(getFeature?.inputSchema.properties).toHaveProperty('detail');
expect(getFeature?.inputSchema.properties).toHaveProperty(
'includeSubData'
);
});
});
describe('Tool Handler', () => {
it('should handle unknown tool error', async () => {
await expect(handleFeaturesTool('unknown_tool', {})).rejects.toThrow(
'Unknown tool: unknown_tool'
);
});
it('should accept valid tool names', () => {
const validTools = [
'create_feature',
'get_features',
'get_feature',
'update_feature',
'delete_feature',
'get_available_fields',
];
validTools.forEach(toolName => {
expect(() => {
// Just check it doesn't throw immediately
const promise = handleFeaturesTool(toolName, {});
// Catch the expected error about missing required fields
promise.catch(() => {});
}).not.toThrow('Unknown features tool');
});
});
it('should include timeframe in update_feature schema', () => {
const tools = setupFeaturesTools();
const updateFeature = tools.find(
(t: ToolDefinition) => t.name === 'update_feature'
);
expect(updateFeature?.inputSchema.properties).toHaveProperty('timeframe');
expect((updateFeature?.inputSchema.properties as any).timeframe).toEqual({
type: 'object',
description: 'Feature timeframe with start and end dates',
properties: {
startDate: {
type: 'string',
description: 'Start date (YYYY-MM-DD)',
},
endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' },
granularity: {
type: 'string',
description: 'Timeframe granularity (optional)',
},
},
});
});
it('should handle update_feature with timeframe parameter', async () => {
// Test that the function accepts timeframe without throwing
expect(() => {
const promise = handleFeaturesTool('update_feature', {
id: 'test-feature-id',
timeframe: {
startDate: '2025-02-01',
endDate: '2025-02-28',
},
});
// Catch expected error about missing API context
promise.catch(() => {});
}).not.toThrow();
});
it('should handle update_feature with timeframe as JSON string', async () => {
// Test that the function accepts timeframe JSON string without throwing
expect(() => {
const promise = handleFeaturesTool('update_feature', {
id: 'test-feature-id',
timeframe: '{"startDate":"2025-02-01","endDate":"2025-02-28"}',
});
// Catch expected error about missing API context
promise.catch(() => {});
}).not.toThrow();
});
});
});