const fs = require('fs'); const path = require('path'); const { extractContracts, extractAllContracts, buildContractXref } = require('../contracts.js'); const GraphStore = require('../graph.js'); const { buildSubsystems, relPath } = require('../subsystem.js'); const FIXTURE_DIR = path.join(__dirname, 'fixtures/system-docs'); const FIXTURE_SRC = path.join(FIXTURE_DIR, 'src'); const SNAPSHOT = path.join(FIXTURE_DIR, 'snapshot.json'); const EXPECTED = JSON.parse(fs.readFileSync(path.join(FIXTURE_DIR, 'expected-contracts.json'), 'utf8')); let passed = 0; let failed = 0; function assert(condition, name) { if (condition) { passed++; console.log(` ✓ ${name}`); } else { failed++; console.log(` ✗ ${name}`); } } console.log('=== 7B: Contract Extractor Tests ===\n'); // Test 1: Interface extraction with fields console.log('Test 1: Interface extraction'); const gwContracts = extractContracts(path.join(FIXTURE_SRC, 'gateway/types.ts'), 'gateway/types.ts'); const gwConfig = gwContracts.find(c => c.name === 'GatewayConfig'); assert(gwConfig !== undefined, 'Found GatewayConfig'); assert(gwConfig.type === 'Interface', 'GatewayConfig is Interface'); assert(gwConfig.fields.length === 2, 'GatewayConfig has 2 fields'); assert(gwConfig.fields.some(f => f.name === 'sessionKey' && f.type === 'string'), 'Has sessionKey: string'); assert(gwConfig.fields.some(f => f.name === 'timeout' && f.type === 'number'), 'Has timeout: number'); // Test 2: Extends clause console.log('\nTest 2: Extends clause'); assert(gwConfig.extends && gwConfig.extends.includes('BaseConfig'), 'GatewayConfig extends BaseConfig'); const sessionEntry = gwContracts.find(c => c.name === 'SessionEntry'); assert(sessionEntry !== undefined, 'Found SessionEntry'); assert(!sessionEntry.extends, 'SessionEntry has no extends'); // Test 3: Type alias console.log('\nTest 3: Type alias'); const agentContracts = extractContracts(path.join(FIXTURE_SRC, 'agents/types.ts'), 'agents/types.ts'); const agentStatus = agentContracts.find(c => c.name === 'AgentStatus'); assert(agentStatus !== undefined, 'Found AgentStatus'); assert(agentStatus.type === 'TypeAlias', 'AgentStatus is TypeAlias'); // Test 4: Enum with members console.log('\nTest 4: Enum extraction'); const configContracts = extractContracts(path.join(FIXTURE_SRC, 'config/types.ts'), 'config/types.ts'); const logLevel = configContracts.find(c => c.name === 'LogLevel'); assert(logLevel !== undefined, 'Found LogLevel'); assert(logLevel.type === 'Enum', 'LogLevel is Enum'); assert(logLevel.members.length === 4, 'LogLevel has 4 members'); assert(logLevel.members.includes('Debug'), 'Has Debug member'); assert(logLevel.members.includes('Error'), 'Has Error member'); // Test 5: Visibility console.log('\nTest 5: Visibility'); assert(gwConfig.visibility === 'public', 'Exported interface is public'); // Test 6: Match expected contracts count console.log('\nTest 6: Total contract count'); const graph = GraphStore.loadSnapshot(SNAPSHOT); const subs = buildSubsystems(graph, { minTraffic: 3, crossCuttingThreshold: 0.6 }); const allResult = extractAllContracts(subs, FIXTURE_SRC); assert(allResult.contracts.length === EXPECTED.contracts.length, `Total contracts: ${allResult.contracts.length} (expected ${EXPECTED.contracts.length})`); // Test 7: By-subsystem grouping console.log('\nTest 7: By-subsystem grouping'); assert(allResult.bySubsystem['gateway'].length === 2, 'gateway has 2 contracts'); assert(allResult.bySubsystem['agents'].length === 4, 'agents has 4 contracts'); assert(allResult.bySubsystem['config'].length === 5, 'config has 5 contracts'); // Test 8: Contract cross-reference console.log('\nTest 8: Contract cross-reference'); const xref = buildContractXref(allResult.contracts, graph, relPath); assert(xref['BaseConfig'] !== undefined, 'BaseConfig in xref'); assert(xref['BaseConfig'].definedIn === 'config', 'BaseConfig defined in config'); assert(xref['BaseConfig'].usedBy.includes('gateway'), 'BaseConfig used by gateway'); // Test 9: Orphan file contracts still extracted console.log('\nTest 9: Orphan file contracts'); const schemaContracts = allResult.bySubsystem['config'].filter(c => c.id.startsWith('config/schema.ts')); assert(schemaContracts.length === 2, 'Orphan schema.ts contracts extracted'); // Test 10: OpenClaw scale test console.log('\nTest 10: OpenClaw scale'); const fullSnap = path.join(__dirname, '..', 'snapshots', 'openclaw-full.json'); if (fs.existsSync(fullSnap)) { const fullGraph = GraphStore.loadSnapshot(fullSnap); const fullSubs = buildSubsystems(fullGraph); const start = Date.now(); const fullResult = extractAllContracts(fullSubs, '/app/src'); const elapsed = Date.now() - start; console.log(` OpenClaw: ${fullResult.contracts.length} contracts in ${elapsed}ms`); assert(fullResult.contracts.length > 50, `Found >50 contracts (${fullResult.contracts.length})`); assert(elapsed < 30000, `Completed in <30s (${elapsed}ms)`); } else { console.log(' (skipped — openclaw-full.json not found)'); } console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`); process.exit(failed > 0 ? 1 : 0);