@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
219 lines (192 loc) • 8.91 kB
text/typescript
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}`);
}
}
});
});