UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

219 lines (192 loc) 8.91 kB
import { describe, it, expect } from 'vitest'; import Ajv from 'ajv'; import addFormats from 'ajv-formats'; // Removed unused imports for new 12-factor pattern import { setupAgileManagementTools } from '../../modules/agile-management/tools.js'; import { setupKanbanTools } from '../../modules/kanban/tools.js'; import { setupDataManagementTools } from '../../modules/data-management/tools.js'; import { setupDocumentationTools } from '../../modules/documentation/tools.js'; import { setupBusinessGuidanceTools } from '../../modules/business-guidance/tools.js'; import { setupCodeAnalysisTools } from '../../modules/code-analysis/tools.js'; import { setupDevelopmentTools } from '../../modules/development/tools.js'; import { setupMemoryManagementTools } from '../../modules/memory-management/tools.js'; describe('JSON Schema Draft 2020-12 Validation', () => { // Create AJV instance with draft 2020-12 support const ajv = new Ajv({ strict: true, validateFormats: true, allErrors: true, }); // Add format validators addFormats(ajv); // No longer need mocks for new 12-factor pattern const testModuleSchemas = async (moduleName: string, setupFunction: Function) => { const module = await setupFunction(); if (!module.tools || module.tools.length === 0) { return; } for (const tool of module.tools) { if (tool.inputSchema) { // Validate the schema itself is valid JSON Schema const schemaSchema = { type: 'object', properties: { type: { type: 'string' }, properties: { type: 'object' }, required: { type: 'array', items: { type: 'string' } } }, required: ['type'] }; const validateSchema = ajv.compile(schemaSchema); const schemaValid = validateSchema(tool.inputSchema); if (!schemaValid) { console.error(`Schema validation errors for ${moduleName} - ${tool.name}:`, validateSchema.errors); } expect(schemaValid, `${moduleName} - ${tool.name} schema structure is invalid`).toBe(true); // Test that the schema can be compiled (validates pattern syntax, etc.) try { const validate = ajv.compile(tool.inputSchema); expect(validate).toBeDefined(); // Test with a sample valid input if (tool.inputSchema.properties) { const sampleInput: any = {}; // Generate sample data based on schema for (const [key, prop] of Object.entries(tool.inputSchema.properties as any)) { if (prop.type === 'string') { // For enum fields, use the first allowed value if (prop.enum && prop.enum.length > 0) { sampleInput[key] = prop.default || prop.enum[0]; } else if (prop.pattern) { // Generate a value that matches the pattern if (prop.pattern === '^[a-zA-Z0-9-]+$') { sampleInput[key] = 'test-id-123'; } else { sampleInput[key] = prop.default || 'test'; } } else { sampleInput[key] = prop.default || 'test'; } } else if (prop.type === 'number' || prop.type === 'integer') { sampleInput[key] = prop.default || 1; } else if (prop.type === 'boolean') { sampleInput[key] = prop.default || false; } else if (prop.type === 'array') { // For arrays with minItems, ensure we meet the requirement if (prop.minItems && prop.minItems > 0) { const items = []; for (let i = 0; i < prop.minItems; i++) { if (prop.items && prop.items.type === 'object') { // Create object with required properties const itemObj: any = {}; if (prop.items.properties) { for (const [itemKey, itemProp] of Object.entries(prop.items.properties as any)) { if (prop.items.required && prop.items.required.includes(itemKey)) { if (itemProp.type === 'string') { itemObj[itemKey] = itemProp.enum ? itemProp.enum[0] : 'test'; } else if (itemProp.type === 'boolean') { itemObj[itemKey] = false; } } } } items.push(itemObj); } else if (prop.items && prop.items.type === 'string') { items.push('test'); } else { items.push(null); } } sampleInput[key] = prop.default || items; } else { sampleInput[key] = prop.default || []; } } else if (prop.type === 'object') { sampleInput[key] = prop.default || {}; } } // Only include required fields if (tool.inputSchema.required && tool.inputSchema.required.length > 0) { const requiredInput: any = {}; for (const field of tool.inputSchema.required) { if (sampleInput[field] !== undefined) { requiredInput[field] = sampleInput[field]; } } const isValid = validate(requiredInput); if (!isValid && validate.errors) { console.error(`Validation errors for ${moduleName} - ${tool.name}:`, validate.errors); } } } } catch (error) { throw new Error(`Failed to compile schema for ${moduleName} - ${tool.name}: ${error.message}`); } } } }; it('should validate agile management module schemas', async () => { await testModuleSchemas('agile-management', setupAgileManagementTools); }); it('should validate kanban module schemas', async () => { await testModuleSchemas('kanban', setupKanbanTools); }); it('should validate data management module schemas', async () => { await testModuleSchemas('data-management', setupDataManagementTools); }); it('should validate documentation module schemas', async () => { await testModuleSchemas('documentation', setupDocumentationTools); }); it('should validate business guidance module schemas', async () => { await testModuleSchemas('business-guidance', setupBusinessGuidanceTools); }); it('should validate code analysis module schemas', async () => { await testModuleSchemas('code-analysis', setupCodeAnalysisTools); }); it('should validate development module schemas', async () => { await testModuleSchemas('development', setupDevelopmentTools); }); it('should validate memory management module schemas', async () => { await testModuleSchemas('memory-management', setupMemoryManagementTools); }); it('should validate pattern regex syntax', async () => { // Test specific patterns that might be problematic const testPatterns = [ { pattern: '^[a-zA-Z0-9 _-]+$', description: 'alphanumeric with space, underscore, hyphen' }, { pattern: '^[a-zA-Z0-9]+$', description: 'alphanumeric only' }, { pattern: '^.+$', description: 'any non-empty string' } ]; for (const { pattern, description } of testPatterns) { const schema = { type: 'object', properties: { name: { type: 'string', pattern: pattern } } }; try { const validate = ajv.compile(schema); expect(validate).toBeDefined(); // Test valid inputs based on pattern if (pattern === '^[a-zA-Z0-9 _-]+$') { // Pattern allows spaces, underscores and hyphens expect(validate({ name: 'Test_Name-123' })).toBe(true); expect(validate({ name: 'Test Name' })).toBe(true); } else if (pattern === '^[a-zA-Z0-9]+$') { // Pattern only allows alphanumeric expect(validate({ name: 'test123' })).toBe(true); expect(validate({ name: 'Test_Name-123' })).toBe(false); expect(validate({ name: 'Test Name' })).toBe(false); } else if (pattern === '^.+$') { // Pattern allows any non-empty string expect(validate({ name: 'Any string!' })).toBe(true); } // Test invalid inputs expect(validate({ name: '' })).toBe(false); } catch (error) { throw new Error(`Pattern validation failed for "${description}": ${error.message}`); } } }); });