test-create-task-validation.ts•10.4 kB
/**
* Test Create Task Endpoint Validation
* This test verifies that the generated MCP tool code for the Create Task endpoint
* correctly matches the expected structure in createTaskValidation.json
*/
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
// Get the directory name
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Import the generateEndpointToolCode function
import generateEndpointToolCode from '../build/services/generateEndpointToolCode.js';
import { validateMCPSchema } from '../src/utils/validateMCPSchema.js';
// Define the interface here to avoid import issues
interface GenerateEndpointToolCodeParams {
path: string;
method: string;
swaggerFilePath: string;
includeApiInName?: boolean;
includeVersionInName?: boolean;
singularizeResourceNames?: boolean;
}
async function testCreateTaskEndpoint() {
console.log('Testing Create Task Endpoint Code Generation...');
try {
// Use the projects-api-v3 Swagger file from ReferenceFiles
const swaggerFilePath = path.join(__dirname, '..', 'ReferenceFiles', 'projects-api-v3.oas2.yml');
if (!fs.existsSync(swaggerFilePath)) {
console.error(`Swagger file not found at ${swaggerFilePath}`);
return false;
}
console.log(`Using Swagger file: ${swaggerFilePath}`);
// Define the Create Task endpoint
const createTaskEndpoint = {
path: '/projects/api/v3/tasklists/{tasklistId}/tasks.json',
method: 'post',
swaggerFilePath,
includeApiInName: false,
includeVersionInName: false,
singularizeResourceNames: true
};
console.log(`Generating tool code for ${createTaskEndpoint.method.toUpperCase()} ${createTaskEndpoint.path}...`);
const tsCode = await generateEndpointToolCode(createTaskEndpoint);
// Check if the response is a validation error message
if (tsCode.includes('MCP Schema Validation Failed')) {
console.error('MCP schema validation failed:');
console.error(tsCode);
return false;
}
// Save the generated code for inspection
const outputDir = path.join(__dirname, 'generated');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const outputPath = path.join(outputDir, 'create-task-endpoint.ts');
fs.writeFileSync(outputPath, tsCode);
console.log(`Generated code saved to: ${outputPath}`);
// Validate the generated code against the MCP schema
const validationResult = validateMCPSchema(tsCode);
if (!validationResult.isValid) {
console.error('Generated code failed MCP schema validation:');
console.error(validationResult.errors);
return false;
}
console.log('Generated code passed MCP schema validation!');
// Load the expected structure from createTaskValidation.json
const expectedStructurePath = path.join(__dirname, 'test-data', 'createTaskValidation.json');
if (!fs.existsSync(expectedStructurePath)) {
console.error(`Expected structure file not found at ${expectedStructurePath}`);
console.error(`Current directory: ${__dirname}`);
console.error(`Files in test-data directory:`);
try {
const testDataDir = path.join(__dirname, 'test-data');
if (fs.existsSync(testDataDir)) {
const files = fs.readdirSync(testDataDir);
files.forEach(file => console.error(`- ${file}`));
} else {
console.error('test-data directory does not exist');
}
} catch (err) {
console.error('Error reading test-data directory:', err);
}
return false;
}
const expectedStructure = JSON.parse(fs.readFileSync(expectedStructurePath, 'utf8'));
console.log('Loaded expected structure from createTaskValidation.json');
// Extract the inputSchema from the generated code
const inputSchemaMatch = tsCode.match(/inputSchema:\s*({[\s\S]*?}),\s*required:/);
if (!inputSchemaMatch) {
console.error('Could not extract inputSchema from generated code');
return false;
}
// Save the raw inputSchema for debugging
const rawInputSchemaPath = path.join(outputDir, 'raw-input-schema.txt');
fs.writeFileSync(rawInputSchemaPath, inputSchemaMatch[1]);
console.log(`Raw inputSchema saved to: ${rawInputSchemaPath}`);
// Instead of trying to parse the complex TypeScript code as JSON,
// let's use a simpler approach to validate the structure
console.log('Validating structure by checking for key properties...');
// Check for top-level properties from the expected structure
const missingTopLevelProps = [];
for (const key in expectedStructure) {
const propRegex = new RegExp(`${key}:\\s*{`, 'i');
if (!propRegex.test(tsCode)) {
missingTopLevelProps.push(key);
}
}
if (missingTopLevelProps.length > 0) {
console.error('❌ Missing top-level properties in the generated code:');
missingTopLevelProps.forEach(prop => {
console.error(`- ${prop}`);
});
return false;
}
console.log('✅ All top-level properties from the expected structure are present in the generated code');
// Check for some key nested properties
const keyProps = [
'name',
'description',
'tasklistId',
'assignees',
'reminders',
'tags',
'taskOptions'
];
const missingProps = [];
for (const prop of keyProps) {
// Use a simpler regex that just checks if the property exists anywhere in the code
const propRegex = new RegExp(`${prop}:\\s*{`, 'i');
if (!propRegex.test(tsCode)) {
missingProps.push(prop);
}
}
if (missingProps.length > 0) {
console.error('❌ Missing properties in the generated code:');
missingProps.forEach(prop => {
console.error(`- ${prop}`);
});
return false;
}
console.log('✅ All key properties are present in the generated code');
// If we've made it this far, the test passes
return true;
} catch (error: any) {
console.error('Error testing Create Task endpoint:', error.message);
if (error.stack) {
console.error(error.stack);
}
return false;
}
}
/**
* Validates that the generated structure matches the expected structure
* @param generated The generated structure from the inputSchema
* @param expected The expected structure from createTaskValidation.json
* @returns Validation result with errors if any
*/
function validateStructure(generated: any, expected: any, path = ''): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
// Check if the expected structure is an object with properties
if (typeof expected === 'object' && expected !== null && !Array.isArray(expected)) {
// For each property in the expected structure
for (const key in expected) {
const currentPath = path ? `${path}.${key}` : key;
// Check if the property exists in the generated structure
if (!generated || !generated[key]) {
errors.push(`Missing property: ${currentPath}`);
continue;
}
// If the property is an object, recursively validate its structure
if (typeof expected[key] === 'object' && expected[key] !== null && !Array.isArray(expected[key])) {
// Check if the property is a type placeholder (e.g., "<string>", "<integer>")
const isTypePlaceholder = Object.keys(expected[key]).some(k =>
typeof expected[key][k] === 'string' &&
expected[key][k].startsWith('<') &&
expected[key][k].endsWith('>')
);
if (isTypePlaceholder) {
// This is a type placeholder, so we don't need to validate its structure
continue;
}
// For properties object in the generated schema
if (generated[key].properties) {
const nestedValidation = validateStructure(generated[key].properties, expected[key], currentPath);
errors.push(...nestedValidation.errors);
}
// For items in arrays
else if (generated[key].items && generated[key].items.properties) {
const nestedValidation = validateStructure(generated[key].items.properties, expected[key], currentPath);
errors.push(...nestedValidation.errors);
}
// For nested types
else if (generated[key].type === 'object' && generated[key].properties) {
const nestedValidation = validateStructure(generated[key].properties, expected[key], currentPath);
errors.push(...nestedValidation.errors);
}
// If we can't find properties, it's an error
else {
errors.push(`Property ${currentPath} should be an object with properties`);
}
}
// If the property is an array, check its items
else if (Array.isArray(expected[key])) {
// Check if the generated property is defined as an array
if (generated[key].type !== 'array') {
errors.push(`Property ${currentPath} should be an array`);
continue;
}
// If the expected array has items, validate the first item's structure
if (expected[key].length > 0 && typeof expected[key][0] === 'object') {
// Check if the generated array has items defined
if (!generated[key].items) {
errors.push(`Property ${currentPath} should have items defined`);
continue;
}
// If the items are objects, validate their structure
if (generated[key].items.type === 'object' && generated[key].items.properties) {
const nestedValidation = validateStructure(generated[key].items.properties, expected[key][0], `${currentPath}[0]`);
errors.push(...nestedValidation.errors);
}
}
}
}
}
return {
isValid: errors.length === 0,
errors
};
}
// Run the test
testCreateTaskEndpoint()
.then(success => {
if (success) {
console.log('\n✅ Create Task endpoint test passed!');
process.exit(0);
} else {
console.error('\n❌ Create Task endpoint test failed!');
process.exit(1);
}
})
.catch(error => {
console.error('Unhandled error:', error);
process.exit(1);
});