{"version":3,"file":"routing-cli.test.d.ts","sourceRoot":"","sources":["../../../src/core/routing/routing-cli.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * CLI integration tests for the routing subsystem.\n *\n * These run the handling functions directly with a temporary routing root so\n * they never touch real agent state and never invoke a provider.\n */\n\nimport { mkdtempSync, rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { afterAll, beforeAll, describe, expect, it } from \"vitest\";\nimport { handleRoutingCommand } from \"./cli.js\";\nimport { activePolicyStatus } from \"./cli-helpers.js\";\nimport { decide, replayDecision } from \"./engine.js\";\nimport { RoutingRpcService } from \"./rpc.js\";\n\nlet root: string;\n\nbeforeAll(() => {\n\troot = mkdtempSync(join(tmpdir(), \"jensen-routing-test-\"));\n\tprocess.env.JENSEN_ROUTING_ROOT = root;\n});\n\nafterAll(() => {\n\trmSync(root, { recursive: true, force: true });\n});\n\nfunction capture(fn: () => Promise<boolean>): Promise<string[]> {\n\tconst out: string[] = [];\n\tconst orig = console.log;\n\tconsole.log = (v: unknown) => out.push(typeof v === \"string\" ? v : JSON.stringify(v));\n\tconsole.error = () => {};\n\treturn fn().then((_r) => {\n\t\tconsole.log = orig;\n\t\treturn out;\n\t});\n}\n\ndescribe(\"CLI routing commands (offline, deterministic)\", () => {\n\tit(\"status returns JSON with active policy toggle\", async () => {\n\t\tconst out = await capture(() => handleRoutingCommand([\"routing\", \"status\"]));\n\t\tconst parsed = JSON.parse(out.join(\"\\n\"));\n\t\texpect(parsed).toHaveProperty(\"schemaVersion\", 1);\n\t\texpect(parsed.activePolicy).toHaveProperty(\"active\");\n\t});\n\n\tit(\"decide produces a durable decision and explain can retrieve it\", async () => {\n\t\tconst { decision } = decide({ task: \"Fix the parser bug across multiple files\" });\n\t\tconst replayed = replayDecision(decision.decisionId);\n\t\texpect(replayed?.decisionId).toBe(decision.decisionId);\n\t\texpect(replayed?.selectedCandidateId).toBe(decision.selectedCandidateId);\n\n\t\tconst out = await capture(() => handleRoutingCommand([\"routing\", \"explain\", decision.decisionId]));\n\t\tconst parsed = JSON.parse(out.join(\"\\n\"));\n\t\texpect(parsed.explanation).toHaveProperty(\"provenance\");\n\t\texpect(parsed.explanation.provenance.policyId).toBe(decision.policyId);\n\t});\n\n\tit(\"replay is zero-effect and reconstructs the decision\", async () => {\n\t\tconst { decision } = decide({ task: \"Investigate the symbol\", evidence: {} });\n\t\tconst out = await capture(() => handleRoutingCommand([\"routing\", \"replay\", decision.decisionId]));\n\t\tconst parsed = JSON.parse(out.join(\"\\n\"));\n\t\texpect(parsed.replay).toBe(\"zero-effect\");\n\t\texpect(parsed.decision.decisionId).toBe(decision.decisionId);\n\t});\n\n\tit(\"features returns a versioned vector\", async () => {\n\t\tconst out = await capture(() => handleRoutingCommand([\"routing\", \"features\"]));\n\t\tconst parsed = JSON.parse(out.join(\"\\n\"));\n\t\texpect(parsed.sampleFeature.schemaVersion).toBe(1);\n\t});\n\n\tit(\"policy list/generate are offline and deterministic\", async () => {\n\t\tconst out = await capture(() => handleRoutingCommand([\"routing\", \"policy\", \"list\"]));\n\t\texpect(Array.isArray(JSON.parse(out.join(\"\\n\")).policies)).toBe(true);\n\t});\n});\n\ndescribe(\"doctor routing\", () => {\n\tit(\"returns checks with pass/fail statuses and nonzero exit on fail\", async () => {\n\t\tlet code = 0;\n\t\tconst orig = process.exitCode;\n\t\tprocess.exitCode = 0;\n\t\tconst origLog = console.log;\n\t\t{\n\t\t\tconst chunks: string[] = [];\n\t\t\tconsole.log = (v: unknown) => chunks.push(typeof v === \"string\" ? v : JSON.stringify(v));\n\t\t\tconsole.error = () => {};\n\t\t}\n\t\tconst result = await handleRoutingCommand([\"doctor\", \"routing\"]);\n\t\tcode = result ? process.exitCode : -1;\n\t\tprocess.exitCode = orig;\n\t\tconsole.log = origLog;\n\t\texpect(code).toBe(0); // healthy => exit 0\n\t});\n});\n\ndescribe(\"Routing RPC service (versioned, read-only + authorized)\", () => {\n\tconst svc = new RoutingRpcService();\n\n\tit(\"rejects unsupported version\", () => {\n\t\tconst r = svc.handle({ version: 99 as never, requestId: \"x\", operation: \"routing.status\" });\n\t\texpect((r as { error: string }).error).toBe(\"invalid_request\");\n\t});\n\n\tit(\"returns status\", () => {\n\t\tconst r = svc.handle({ version: 1, requestId: \"x\", operation: \"routing.status\" });\n\t\texpect((r as { operation: string }).operation).toBe(\"routing.status\");\n\t});\n\n\tit(\"decide requires task\", () => {\n\t\tconst r = svc.handle({ version: 1, requestId: \"x\", operation: \"routing.decide\", parameters: {} });\n\t\texpect((r as { error: string }).error).toBe(\"invalid_request\");\n\t});\n\n\tit(\"decide returns a durable decision\", () => {\n\t\tconst r = svc.handle({\n\t\t\tversion: 1,\n\t\t\trequestId: \"x\",\n\t\t\toperation: \"routing.decide\",\n\t\t\tparameters: { task: \"Fix the timeout in the scheduler\" },\n\t\t});\n\t\tconst data = (r as { data: { decision: { decisionId: string } } }).data;\n\t\texpect(replayDecision(data.decision.decisionId)).toBeDefined();\n\t});\n\n\tit(\"policy promote is blocked when not authorized\", () => {\n\t\tconst r = svc.handle({\n\t\t\tversion: 1,\n\t\t\trequestId: \"x\",\n\t\t\toperation: \"routing.policyPromote\",\n\t\t\tparameters: { policyId: \"whatever\" },\n\t\t});\n\t\texpect((r as { error: string }).error).toBe(\"unauthorized\");\n\t});\n\n\tit(\"features returns a versioned vector over RPC\", () => {\n\t\tconst r = svc.handle({\n\t\t\tversion: 1,\n\t\t\trequestId: \"x\",\n\t\t\toperation: \"routing.features\",\n\t\t\tparameters: { task: \"Fix the bug\" },\n\t\t});\n\t\tconst data = (r as { data: { features: { schemaVersion: number } } }).data;\n\t\texpect(data.features.schemaVersion).toBe(1);\n\t});\n});\n\ndescribe(\"active policy pointer is durable\", () => {\n\tit(\"reflects promotion state\", () => {\n\t\tconst status = activePolicyStatus();\n\t\texpect(status).toHaveProperty(\"active\");\n\t});\n});\n"]}