- Add EXECUTOR_SPECIFICATION.md with formal v1.0 protocol spec - Add executor-openapi.yaml (OpenAPI 3.0 specification) - Create @tpmjs/executor-test compliance test package (15 tests) - Update Railway executor to v1.0 compliance (15/15 tests pass) - Update Unsandbox executor to v1.0 compliance (15/15 tests pass) - Update Vercel executor to v1.0 compliance - Add /info endpoint with capability advertisement to all executors - Add structured error codes (PACKAGE_NOT_FOUND, TOOL_NOT_FOUND, etc.) - Add protocolVersion and implementationVersion to /health responses - Add X-TPMJS-Protocol-Version header support - Add EXECUTOR_COMPLIANCE.md with test results documentation
201 lines
5 KiB
TypeScript
201 lines
5 KiB
TypeScript
import pc from 'picocolors';
|
|
import { runCoreTests } from './tests/core.js';
|
|
import { runStandardTests } from './tests/standard.js';
|
|
import type { ComplianceResult, TestSuite } from './types.js';
|
|
|
|
const VERSION = '0.1.0';
|
|
const PROTOCOL_VERSION = '1.0';
|
|
|
|
function printUsage(): void {
|
|
console.log(`
|
|
${pc.bold('TPMJS Executor Compliance Test')} v${VERSION}
|
|
|
|
${pc.dim('Usage:')}
|
|
npx @tpmjs/executor-test <executor-url> [options]
|
|
|
|
${pc.dim('Options:')}
|
|
--api-key <key> API key for authentication (Bearer token)
|
|
--json Output results as JSON
|
|
--verbose Show detailed test output
|
|
--help Show this help message
|
|
|
|
${pc.dim('Examples:')}
|
|
npx @tpmjs/executor-test https://my-executor.example.com
|
|
npx @tpmjs/executor-test https://my-executor.example.com --api-key sk-xxx
|
|
npx @tpmjs/executor-test https://my-executor.example.com --json
|
|
`);
|
|
}
|
|
|
|
function printBanner(target: string): void {
|
|
console.log();
|
|
console.log(pc.bold(`TPMJS Executor Compliance Test v${VERSION}`));
|
|
console.log(pc.dim(`Protocol Version: ${PROTOCOL_VERSION}`));
|
|
console.log(pc.dim(`Target: ${target}`));
|
|
console.log();
|
|
}
|
|
|
|
function printSuite(suite: TestSuite): void {
|
|
const levelLabel =
|
|
suite.level === 'core'
|
|
? pc.blue('Core')
|
|
: suite.level === 'standard'
|
|
? pc.yellow('Standard')
|
|
: pc.magenta('Extended');
|
|
|
|
console.log(`${levelLabel} ${pc.bold(suite.name)}:`);
|
|
|
|
for (const result of suite.results) {
|
|
const icon = result.passed ? pc.green('\u2713') : pc.red('\u2717');
|
|
const name = result.passed ? result.name : pc.red(result.name);
|
|
const duration = pc.dim(`(${result.durationMs}ms)`);
|
|
|
|
console.log(` ${icon} ${name} ${duration}`);
|
|
|
|
if (!result.passed && result.message) {
|
|
console.log(` ${pc.dim(result.message)}`);
|
|
}
|
|
}
|
|
console.log();
|
|
}
|
|
|
|
function printSummary(result: ComplianceResult): void {
|
|
const { summary } = result;
|
|
|
|
console.log(pc.bold('Summary:'));
|
|
console.log(
|
|
` Tests: ${pc.green(`${summary.passed} passed`)}, ${summary.failed > 0 ? pc.red(`${summary.failed} failed`) : `${summary.failed} failed`}, ${summary.totalTests} total`
|
|
);
|
|
console.log();
|
|
|
|
const coreStatus = summary.coreCompliant ? pc.green('PASS') : pc.red('FAIL');
|
|
const standardStatus = summary.standardCompliant
|
|
? pc.green('PASS')
|
|
: summary.coreCompliant
|
|
? pc.yellow('PARTIAL')
|
|
: pc.red('FAIL');
|
|
|
|
console.log(` Core Compliance: ${coreStatus}`);
|
|
console.log(` Standard Compliance: ${standardStatus}`);
|
|
console.log();
|
|
}
|
|
|
|
interface Options {
|
|
apiKey?: string;
|
|
json: boolean;
|
|
verbose: boolean;
|
|
}
|
|
|
|
function parseArgs(args: string[]): { url: string | null; options: Options } {
|
|
const options: Options = {
|
|
json: false,
|
|
verbose: false,
|
|
};
|
|
|
|
let url: string | null = null;
|
|
|
|
for (let i = 0; i < args.length; i++) {
|
|
const arg = args[i];
|
|
|
|
if (arg === '--help' || arg === '-h') {
|
|
printUsage();
|
|
process.exit(0);
|
|
}
|
|
|
|
if (arg === '--json') {
|
|
options.json = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg === '--verbose' || arg === '-v') {
|
|
options.verbose = true;
|
|
continue;
|
|
}
|
|
|
|
if (arg === '--api-key') {
|
|
i++;
|
|
options.apiKey = args[i] ?? undefined;
|
|
continue;
|
|
}
|
|
|
|
if (!arg?.startsWith('-') && !url) {
|
|
url = arg ?? null;
|
|
}
|
|
}
|
|
|
|
return { url, options };
|
|
}
|
|
|
|
export async function run(args: string[]): Promise<void> {
|
|
const { url, options } = parseArgs(args);
|
|
|
|
if (!url) {
|
|
printUsage();
|
|
process.exit(1);
|
|
}
|
|
|
|
// Normalize URL
|
|
const target = url.replace(/\/$/, '');
|
|
|
|
if (!options.json) {
|
|
printBanner(target);
|
|
}
|
|
|
|
const suites: TestSuite[] = [];
|
|
|
|
// Run Core tests
|
|
if (!options.json) {
|
|
console.log(pc.dim('Running Core compliance tests...'));
|
|
console.log();
|
|
}
|
|
|
|
const coreSuite = await runCoreTests(target, options.apiKey);
|
|
suites.push(coreSuite);
|
|
|
|
if (!options.json) {
|
|
printSuite(coreSuite);
|
|
}
|
|
|
|
// Run Standard tests
|
|
if (!options.json) {
|
|
console.log(pc.dim('Running Standard compliance tests...'));
|
|
console.log();
|
|
}
|
|
|
|
const standardSuite = await runStandardTests(target, options.apiKey);
|
|
suites.push(standardSuite);
|
|
|
|
if (!options.json) {
|
|
printSuite(standardSuite);
|
|
}
|
|
|
|
// Calculate summary
|
|
const totalTests = suites.reduce((sum, s) => sum + s.results.length, 0);
|
|
const passed = suites.reduce((sum, s) => sum + s.results.filter((r) => r.passed).length, 0);
|
|
const failed = totalTests - passed;
|
|
|
|
const coreCompliant = coreSuite.results.every((r) => r.passed);
|
|
const standardCompliant = coreCompliant && standardSuite.results.every((r) => r.passed);
|
|
|
|
const result: ComplianceResult = {
|
|
target,
|
|
protocolVersion: PROTOCOL_VERSION,
|
|
timestamp: new Date().toISOString(),
|
|
suites,
|
|
summary: {
|
|
totalTests,
|
|
passed,
|
|
failed,
|
|
coreCompliant,
|
|
standardCompliant,
|
|
},
|
|
};
|
|
|
|
if (options.json) {
|
|
console.log(JSON.stringify(result, null, 2));
|
|
} else {
|
|
printSummary(result);
|
|
}
|
|
|
|
// Exit with error code if not compliant
|
|
process.exit(coreCompliant ? 0 : 1);
|
|
}
|