#!/usr/bin/env node /** * Unit Tests - Agent Definitions * * Tests programmatic agent definitions with tool restrictions * * @version 2.0.0 * @date 2025-11-13 */ import assert from 'assert'; import { getAgentDefinition, getAllAgents, getAgentsByTool, getAgentsByModel, validateAllAgents, getAgentCostEstimate, generateAgentReport, TOOL_SETS } from '../../tools/agents/agent-definitions.mjs'; // ============================================================================ // Test Suite // ============================================================================ const tests = { async testAgentDefinitionRetrieval() { console.log('\n๐Ÿงช Test: Agent Definition Retrieval'); const analyst = getAgentDefinition('analyst'); assert(analyst, 'Should retrieve analyst definition'); assert.strictEqual(analyst.name, 'analyst'); assert.strictEqual(analyst.title, 'Business Analyst'); assert(analyst.tools.length > 0, 'Should have tools defined'); console.log(' โœ“ PASSED'); }, async testToolRestrictions() { console.log('\n๐Ÿงช Test: Tool Restrictions'); const analyst = getAgentDefinition('analyst'); const developer = getAgentDefinition('developer'); const qa = getAgentDefinition('qa'); // Analyst should only have read-only tools assert.deepStrictEqual(analyst.tools, TOOL_SETS.READ_ONLY); console.log(` โœ“ Analyst has read-only tools: ${analyst.tools.join(', ')}`); // Developer should have development tools assert.deepStrictEqual(developer.tools, TOOL_SETS.DEVELOPMENT); console.log(` โœ“ Developer has development tools: ${developer.tools.join(', ')}`); // QA should have testing tools assert.deepStrictEqual(qa.tools, TOOL_SETS.TESTING); console.log(` โœ“ QA has testing tools: ${qa.tools.join(', ')}`); console.log(' โœ“ PASSED'); }, async testModelSelection() { console.log('\n๐Ÿงช Test: Model Selection'); const qa = getAgentDefinition('qa'); const analyst = getAgentDefinition('analyst'); const orchestrator = getAgentDefinition('bmad-orchestrator'); // QA should use Haiku (cost optimization for routine tasks) assert.strictEqual(qa.model, 'claude-haiku-4'); console.log(` โœ“ QA uses Haiku: ${qa.model}`); // Analyst should use Sonnet (complex analysis) assert.strictEqual(analyst.model, 'claude-sonnet-4-5'); console.log(` โœ“ Analyst uses Sonnet: ${analyst.model}`); // Orchestrator should use Opus (premium coordination) assert.strictEqual(orchestrator.model, 'claude-opus-4-1'); console.log(` โœ“ Orchestrator uses Opus: ${orchestrator.model}`); console.log(' โœ“ PASSED'); }, async testCostEstimation() { console.log('\n๐Ÿงช Test: Cost Estimation'); const haikuCost = getAgentCostEstimate('qa', 10000, 2000); const sonnetCost = getAgentCostEstimate('analyst', 10000, 2000); const opusCost = getAgentCostEstimate('bmad-orchestrator', 10000, 2000); console.log(` ๐Ÿ’ฐ Haiku cost: $${haikuCost.estimated_cost.toFixed(6)}`); console.log(` ๐Ÿ’ฐ Sonnet cost: $${sonnetCost.estimated_cost.toFixed(6)}`); console.log(` ๐Ÿ’ฐ Opus cost: $${opusCost.estimated_cost.toFixed(6)}`); // Haiku should be cheaper than Sonnet assert(haikuCost.estimated_cost < sonnetCost.estimated_cost, 'Haiku should be cheaper than Sonnet'); // Sonnet should be cheaper than Opus assert(sonnetCost.estimated_cost < opusCost.estimated_cost, 'Sonnet should be cheaper than Opus'); console.log(' โœ“ PASSED'); }, async testAgentValidation() { console.log('\n๐Ÿงช Test: Agent Validation'); const results = validateAllAgents(); console.log(` โœ“ Valid agents: ${results.valid.length}`); console.log(` โœ“ Invalid agents: ${results.invalid.length}`); if (results.invalid.length > 0) { console.error(' โœ— Invalid agents found:'); for (const invalid of results.invalid) { console.error(` - ${invalid.name}: ${invalid.error}`); } } assert(results.valid.length > 0, 'Should have valid agents'); assert.strictEqual(results.invalid.length, 0, 'Should have no invalid agents'); console.log(' โœ“ PASSED'); }, async testAgentQueryByTool() { console.log('\n๐Ÿงช Test: Query Agents by Tool'); const readAgents = getAgentsByTool('Read'); const bashAgents = getAgentsByTool('Bash'); const editAgents = getAgentsByTool('Edit'); console.log(` โœ“ Agents with Read tool: ${readAgents.map(a => a.name).join(', ')}`); console.log(` โœ“ Agents with Bash tool: ${bashAgents.map(a => a.name).join(', ')}`); console.log(` โœ“ Agents with Edit tool: ${editAgents.map(a => a.name).join(', ')}`); assert(readAgents.length > 0, 'Should have agents with Read tool'); assert(bashAgents.length > 0, 'Should have agents with Bash tool'); assert(editAgents.length > 0, 'Should have agents with Edit tool'); console.log(' โœ“ PASSED'); }, async testAgentQueryByModel() { console.log('\n๐Ÿงช Test: Query Agents by Model'); const haikuAgents = getAgentsByModel('claude-haiku-4'); const sonnetAgents = getAgentsByModel('claude-sonnet-4-5'); const opusAgents = getAgentsByModel('claude-opus-4-1'); console.log(` โœ“ Haiku agents: ${haikuAgents.map(a => a.name).join(', ')}`); console.log(` โœ“ Sonnet agents: ${sonnetAgents.map(a => a.name).join(', ')}`); console.log(` โœ“ Opus agents: ${opusAgents.map(a => a.name).join(', ')}`); console.log(' โœ“ PASSED'); }, async testAgentReport() { console.log('\n๐Ÿงช Test: Agent Usage Report'); const report = generateAgentReport(); console.log(` โœ“ Total agents: ${report.total_agents}`); console.log(` โœ“ Haiku agents: ${report.cost_optimization.haiku_agents.join(', ')}`); console.log(` โœ“ Sonnet agents: ${report.cost_optimization.sonnet_agents.join(', ')}`); console.log(` โœ“ Opus agents: ${report.cost_optimization.opus_agents.join(', ')}`); assert(report.total_agents > 0, 'Should have agents'); assert(Object.keys(report.by_model).length > 0, 'Should have model groupings'); console.log(' โœ“ PASSED'); }, async testAgentCapabilities() { console.log('\n๐Ÿงช Test: Agent Capabilities'); const developer = getAgentDefinition('developer'); const architect = getAgentDefinition('architect'); assert(developer.capabilities.length > 0, 'Developer should have capabilities'); assert(architect.capabilities.length > 0, 'Architect should have capabilities'); console.log(` โœ“ Developer capabilities: ${developer.capabilities.length}`); console.log(` โœ“ Architect capabilities: ${architect.capabilities.length}`); console.log(' โœ“ PASSED'); }, async testSystemPromptLoading() { console.log('\n๐Ÿงช Test: System Prompt Loading'); const analyst = getAgentDefinition('analyst'); // Load system prompt const systemPrompt = await analyst.loadSystemPrompt(); assert(systemPrompt, 'Should load system prompt'); assert(systemPrompt.length > 0, 'System prompt should not be empty'); assert(systemPrompt.includes('Analyst'), 'Should contain agent identity'); console.log(` โœ“ Loaded system prompt: ${systemPrompt.length} characters`); console.log(' โœ“ PASSED'); } }; // ============================================================================ // Test Runner // ============================================================================ async function runTests() { console.log('============================================================================'); console.log('Agent Definitions - Unit Tests'); console.log('============================================================================'); let passed = 0; let failed = 0; for (const [name, test] of Object.entries(tests)) { try { await test(); passed++; } catch (error) { console.error(` โœ— FAILED: ${error.message}`); console.error(error.stack); failed++; } } console.log('\n============================================================================'); console.log(`Results: ${passed} passed, ${failed} failed`); console.log('============================================================================\n'); process.exit(failed > 0 ? 1 : 0); } // Run tests if executed directly if (import.meta.url === `file://${process.argv[1]}`) { runTests(); } export { tests, runTests };