UNPKG

mcp-server-tester-sse-http-stdio

Version:

MCP Server Tester with SSE support - Test MCP servers using HTTP, SSE, and STDIO transports

151 lines (150 loc) 6.8 kB
/** * JUnit XML validation utilities * Uses fast-xml-parser for proper XML validation * Validates against canonical JUnit XML schema structure */ import { readFileSync } from 'fs'; import { XMLParser, XMLValidator } from 'fast-xml-parser'; function getAttributeFromElement(element, attributeName) { if (!element) { return undefined; } if (Array.isArray(element)) { return element[0]?.[attributeName]; } return element[attributeName]; } /** * Validates a JUnit XML file using fast-xml-parser */ export function validateJunitXml(filePath) { try { const content = readFileSync(filePath, 'utf8'); return validateJunitXmlContent(content); } catch (error) { return { valid: false, errors: [`Failed to read file: ${error instanceof Error ? error.message : 'Unknown error'}`], warnings: [], }; } } /** * Validates JUnit XML content string using fast-xml-parser */ export function validateJunitXmlContent(content) { const result = { valid: true, errors: [], warnings: [], }; // First, validate XML syntax const validationResult = XMLValidator.validate(content); if (validationResult !== true) { result.valid = false; result.errors.push(`Invalid XML: ${validationResult.err.msg}`); return result; } // Parse the XML to validate JUnit structure try { const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_', }); const parsed = parser.parse(content); // Check for testsuites root element if (!parsed.testsuites) { result.errors.push('Missing <testsuites> root element'); result.valid = false; return result; } // Validate testsuites attributes according to canonical schema const testsuites = parsed.testsuites; if (!testsuites['@_tests'] && testsuites['@_tests'] !== 0) { result.warnings.push('<testsuites> missing tests attribute'); } if (!testsuites['@_failures'] && testsuites['@_failures'] !== 0) { result.warnings.push('<testsuites> missing failures attribute'); } if (!testsuites['@_errors'] && testsuites['@_errors'] !== 0) { result.warnings.push('<testsuites> missing errors attribute'); } if (!testsuites['@_time'] && testsuites['@_time'] !== 0) { result.warnings.push('<testsuites> missing time attribute'); } // Check testsuite elements if (testsuites.testsuite) { const testsuiteArray = Array.isArray(testsuites.testsuite) ? testsuites.testsuite : [testsuites.testsuite]; testsuiteArray.forEach((testsuite, index) => { // Required attributes according to canonical schema if (!testsuite['@_name']) { result.errors.push(`<testsuite> ${index + 1} missing required name attribute`); result.valid = false; } if (!testsuite['@_tests'] && testsuite['@_tests'] !== 0) { result.warnings.push(`<testsuite> ${index + 1} missing tests attribute`); } if (!testsuite['@_failures'] && testsuite['@_failures'] !== 0) { result.warnings.push(`<testsuite> ${index + 1} missing failures attribute`); } if (!testsuite['@_errors'] && testsuite['@_errors'] !== 0) { result.warnings.push(`<testsuite> ${index + 1} missing errors attribute`); } if (!testsuite['@_time'] && testsuite['@_time'] !== 0) { result.warnings.push(`<testsuite> ${index + 1} missing time attribute`); } // Optional but commonly expected attributes if (!testsuite['@_timestamp']) { result.warnings.push(`<testsuite> ${index + 1} missing timestamp attribute`); } // Check testcase elements if (testsuite.testcase) { const testcaseArray = Array.isArray(testsuite.testcase) ? testsuite.testcase : [testsuite.testcase]; testcaseArray.forEach((testcase, testcaseIndex) => { // Required attributes according to canonical schema if (!testcase['@_name']) { result.errors.push(`<testcase> ${testcaseIndex + 1} in <testsuite> ${index + 1} missing required name attribute`); result.valid = false; } if (!testcase['@_classname']) { result.warnings.push(`<testcase> ${testcaseIndex + 1} in <testsuite> ${index + 1} missing classname attribute`); } if (!testcase['@_time'] && testcase['@_time'] !== 0) { result.warnings.push(`<testcase> ${testcaseIndex + 1} in <testsuite> ${index + 1} missing time attribute`); } // Validate failure/error elements structure if (testcase.failure) { if (!getAttributeFromElement(testcase.failure, '@_message')) { result.warnings.push(`<failure> in <testcase> ${testcaseIndex + 1} missing message attribute`); } if (!getAttributeFromElement(testcase.failure, '@_type')) { result.warnings.push(`<failure> in <testcase> ${testcaseIndex + 1} missing type attribute`); } } if (testcase.error) { if (!getAttributeFromElement(testcase.error, '@_message')) { result.warnings.push(`<error> in <testcase> ${testcaseIndex + 1} missing message attribute`); } if (!getAttributeFromElement(testcase.error, '@_type')) { result.warnings.push(`<error> in <testcase> ${testcaseIndex + 1} missing type attribute`); } } }); } }); } else { result.warnings.push('No <testsuite> elements found'); } } catch (error) { result.valid = false; result.errors.push(`Failed to parse XML: ${error instanceof Error ? error.message : 'Unknown error'}`); } return result; }