Skip to main content
Glama

Context Pods

by conorluddy
test-runner.tsโ€ข6.23 kB
/** * Test runner utility */ import { logger } from '@context-pods/core'; import { MCPComplianceTestSuite } from '../protocol/compliance.js'; import type { TestSuiteResult, WrapperTestConfig, TestRunResult } from '../types.js'; import { TestStatus } from '../types.js'; import { ScriptWrapperTester } from '../wrappers/wrapper-tester.js'; /** * Main test runner for the testing framework */ export class TestRunner { private suites: TestSuite[] = []; private config: TestRunnerConfig; constructor(config: TestRunnerConfig = {}) { this.config = { parallel: false, bail: false, timeout: 30000, retries: 0, ...config, }; } /** * Add test suite */ addSuite(suite: TestSuite): void { this.suites.push(suite); } /** * Run all test suites */ async runAll(): Promise<TestRunResult> { const startTime = Date.now(); const results: TestSuiteResult[] = []; logger.info(`Running ${this.suites.length} test suites`); if (this.config.parallel) { // Run suites in parallel const promises = this.suites.map((suite) => this.runSuiteWithRetries(suite)); const suiteResults = await Promise.allSettled(promises); for (const result of suiteResults) { if (result.status === 'fulfilled') { results.push(result.value); } else { results.push({ name: 'Failed Suite', tests: [ { name: 'Suite Execution', status: TestStatus.FAILED, duration: 0, error: (result.reason as Error)?.message || 'Unknown error', }, ], duration: 0, passed: 0, failed: 1, skipped: 0, }); } } } else { // Run suites sequentially for (const suite of this.suites) { try { const result = await this.runSuiteWithRetries(suite); results.push(result); if (this.config.bail && result.failed > 0) { logger.warn('Stopping test execution due to failure (bail mode)'); break; } } catch (error) { const failedResult: TestSuiteResult = { name: suite.name, tests: [ { name: 'Suite Execution', status: TestStatus.FAILED, duration: 0, error: error instanceof Error ? error.message : String(error), }, ], duration: 0, passed: 0, failed: 1, skipped: 0, }; results.push(failedResult); if (this.config.bail) { break; } } } } // Calculate totals const totalTests = results.reduce((sum, suite) => sum + suite.tests.length, 0); const totalPassed = results.reduce((sum, suite) => sum + suite.passed, 0); const totalFailed = results.reduce((sum, suite) => sum + suite.failed, 0); const totalSkipped = results.reduce((sum, suite) => sum + suite.skipped, 0); logger.info( `Test run completed: ${totalPassed} passed, ${totalFailed} failed, ${totalSkipped} skipped`, ); return { suites: results, duration: Date.now() - startTime, totalTests, totalPassed, totalFailed, totalSkipped, success: totalFailed === 0, }; } /** * Run a single suite with retries */ private async runSuiteWithRetries(suite: TestSuite): Promise<TestSuiteResult> { let lastError: Error | undefined; for (let attempt = 0; attempt <= (this.config.retries || 0); attempt++) { try { if (attempt > 0) { logger.info(`Retrying suite: ${suite.name} (attempt ${attempt + 1})`); } return await this.runSuite(suite); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt < (this.config.retries || 0)) { logger.warn(`Suite failed, retrying: ${lastError.message}`); } } } throw new Error(lastError ? lastError.message : 'All test attempts failed'); } /** * Run a single test suite */ private async runSuite(suite: TestSuite): Promise<TestSuiteResult> { logger.info(`Running test suite: ${suite.name}`); switch (suite.type) { case 'mcp-compliance': { const complianceSuite = new MCPComplianceTestSuite( suite.serverPath || '', this.config.debug, ); return await complianceSuite.runFullSuite(); } case 'script-wrapper': { if (!suite.wrapperConfig) { throw new Error('Script wrapper suite requires wrapperConfig'); } const wrapperTester = new ScriptWrapperTester(suite.wrapperConfig); return await wrapperTester.runTests(); } case 'custom': if (!suite.runner) { throw new Error('Custom suite requires a runner function'); } return await suite.runner(); default: throw new Error(`Unknown suite type: ${String(suite.type)}`); } } /** * Create MCP compliance test suite */ static createMCPComplianceSuite(name: string, serverPath: string): TestSuite { return { name, type: 'mcp-compliance', serverPath, }; } /** * Create script wrapper test suite */ static createWrapperSuite(name: string, config: WrapperTestConfig): TestSuite { return { name, type: 'script-wrapper', wrapperConfig: config, }; } /** * Create custom test suite */ static createCustomSuite(name: string, runner: () => Promise<TestSuiteResult>): TestSuite { return { name, type: 'custom', runner, }; } } /** * Test suite configuration */ export interface TestSuite { name: string; type: 'mcp-compliance' | 'script-wrapper' | 'custom'; serverPath?: string; wrapperConfig?: WrapperTestConfig; runner?: () => Promise<TestSuiteResult>; } /** * Test runner configuration */ export interface TestRunnerConfig { parallel?: boolean; bail?: boolean; timeout?: number; retries?: number; debug?: boolean; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/conorluddy/ContextPods'

If you have feedback or need assistance with the MCP directory API, please join our Discord server