Files
proffesor-for-testing__agen…/tests/unit/mcp/routing-economics-handler.test.ts
T
Dragan Spiridonov 73eab06585 feat: implement Six Hats Tier 3 Phase 1 — YAML pipelines, heartbeat CLI, economic routing (#334)
Three strategic features from Issue #334 with full CLI-MCP parity:

Imp-9: YAML Deterministic QE Pipelines
- Approval gate step type with auto-approve timeout and cancel cleanup
- 4 deterministic actions (quality-gate, coverage, pattern-health, routing)
  with real SQL queries and graceful DB degradation
- 7 CLI commands (aqe pipeline load/validate/run/list/status/approve/reject)
- YAML loader validation for approval fields

Imp-10: Token-Free Heartbeat CLI & MCP
- 6 CLI commands (aqe heartbeat status/run-now/history/log/pause/resume)
- 3 MCP tools (heartbeat_status/trigger/log)
- History persistence, date validation, timeout support

Imp-18: Economic Routing Model
- EconomicRoutingModel with quality-per-dollar scoring and EMA estimates
- Budget-aware tier selection with fallback strategies
- Cost-adjusted rewards for neural router (opt-in)
- CLI commands (aqe routing economics/accuracy/metrics)
- routing_economics MCP tool with kv_store persistence

Security: path traversal fix on CLI log reader, config weight validation

71 new tests across 6 test files, all passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:09:55 +00:00

100 lines
3.5 KiB
TypeScript

/**
* Unit tests for the routing_economics MCP handler (Imp-18)
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock unified memory — keep all real exports but stub the singleton
vi.mock('../../../src/kernel/unified-memory.js', async (importOriginal) => {
const actual = await importOriginal<Record<string, unknown>>();
const mockDb = {
prepare: vi.fn().mockReturnValue({
get: vi.fn().mockReturnValue(undefined),
all: vi.fn().mockReturnValue([]),
run: vi.fn(),
}),
};
return {
...actual,
getUnifiedMemory: vi.fn().mockReturnValue({
isInitialized: vi.fn().mockReturnValue(true),
initialize: vi.fn().mockResolvedValue(undefined),
getDatabase: vi.fn().mockReturnValue(mockDb),
}),
initializeUnifiedMemory: vi.fn().mockResolvedValue({
isInitialized: () => true,
initialize: async () => {},
getDatabase: () => mockDb,
}),
};
});
// Mock cost tracker — keep real exports but stub the global singleton
vi.mock('../../../src/shared/llm/cost-tracker.js', async (importOriginal) => {
const actual = await importOriginal<Record<string, unknown>>();
return {
...actual,
getGlobalCostTracker: vi.fn().mockReturnValue({
getCurrentCost: vi.fn().mockReturnValue(0),
getSummary: vi.fn().mockReturnValue({
totalCost: 0, totalTokens: 0, totalRequests: 0,
period: 'all', periodStart: new Date(0), periodEnd: new Date(),
byProvider: {}, byModel: {},
}),
}),
};
});
import { handleRoutingEconomics } from '../../../src/mcp/handlers/task-handlers.js';
describe('handleRoutingEconomics MCP handler', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should return a successful economic report', async () => {
const result = await handleRoutingEconomics({ taskComplexity: 0.5 });
expect(result.success).toBe(true);
expect(result.data).toBeDefined();
expect(result.data!.tierEfficiency).toBeInstanceOf(Array);
expect(result.data!.tierEfficiency.length).toBe(4);
expect(typeof result.data!.currentHourlyCostUsd).toBe('number');
expect(typeof result.data!.currentDailyCostUsd).toBe('number');
expect(typeof result.data!.recommendation).toBe('string');
});
it('should include budget remaining as null when no limits set', async () => {
const result = await handleRoutingEconomics({ taskComplexity: 0.3 });
expect(result.success).toBe(true);
expect(result.data!.budgetRemaining).toBeDefined();
expect(result.data!.budgetRemaining.hourly).toBeNull();
expect(result.data!.budgetRemaining.daily).toBeNull();
});
it('should serialize Infinity qualityPerDollar for booster tier', async () => {
const result = await handleRoutingEconomics({ taskComplexity: 0.1 });
expect(result.success).toBe(true);
const booster = result.data!.tierEfficiency.find(
(t: Record<string, unknown>) => t.tier === 'booster',
);
expect(booster).toBeDefined();
// Infinity should be serialized as string for JSON safety
expect(booster!.qualityPerDollar).toBe('Infinity');
});
it('should return tiers sorted by economic score descending', async () => {
const result = await handleRoutingEconomics({ taskComplexity: 0.5 });
expect(result.success).toBe(true);
const scores = result.data!.tierEfficiency.map(
(t: Record<string, unknown>) => t.economicScore as number,
);
for (let i = 1; i < scores.length; i++) {
expect(scores[i - 1]).toBeGreaterThanOrEqual(scores[i]);
}
});
});