Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"pretest": "tsc --noEmit",
"test": "tsx --test tests/*.test.ts tests/functional/*.test.ts",
"test:functional": "tsx --test --test-timeout=120000 --test-concurrency=1 tests/functional/*.test.ts",
"test:claims": "tsx --test --test-timeout=60000 --test-concurrency=1 tests/claims/suite.test.ts",
"test:hermes": "tsx --test --test-timeout=180000 tests/hermes-user-path.test.ts",
"start": "node dist/cli.js"
},
Expand Down
150 changes: 150 additions & 0 deletions tests/claims/catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* Every product claim Fusion makes in README / console docs, scored as:
* function — the surface exists (CLI, MCP, UI, types)
* value — the behavior matches the promise
* Source: README.md (2026-09). Local suite only — not CI.
*/

export type ClaimKind = "function" | "value";

export type Claim = {
id: string;
claim: string;
source: string;
};

export const CLAIMS: Claim[] = [
{
id: "clients-four",
claim: "One layer over Claude Code, Codex, Cursor, Hermes into Langfuse",
source: "README opening",
},
{
id: "capture-split",
claim: "Claude Code and Codex share the OTLP bridge; Cursor and Hermes share the gateway",
source: "README Today vs Fusion / Fusion Clients",
},
{
id: "loopback",
claim: "Fusion stays on this machine (127.0.0.1)",
source: "README opening",
},
{
id: "init-sinks",
claim: "fusion init finds existing Langfuse, then use-local | use-cloud | docker-local | cloud | gateway-only",
source: "README Setup step 1",
},
{
id: "discover-no-hardcoded-port",
claim: "Discover does not invent a Langfuse port; host --local is only for a stack Fusion starts",
source: "README Setup / coverage noSinkDetail",
},
{
id: "ports",
claim: "up exposes UI/control :4599, gateway :4600, OTLP bridge :4318",
source: "README Setup step 2",
},
{
id: "cli-verbs",
claim: "CLI offers init, host, up/down, enable/disable, project/route, connect, doctor/status, ui, config",
source: "README Setup CLI",
},
{
id: "enable-sources",
claim: "fusion enable wires claude-code, codex, or hermes (Cursor is gateway, not enable)",
source: "README Fusion Clients / Capture",
},
{
id: "project-stamp",
claim: ".fusion + hook (or gateway) stamp project= at the source; Fusion does not guess it later from the trace",
source: "README Today vs Fusion",
},
{
id: "coverage-states",
claim: "Coverage is FLOWING / CONFIGURED / SUBSCRIPTION / BYPASSED / DOWN (and unknown when Langfuse cannot be queried)",
source: "README MCP / UI",
},
{
id: "subscription-not-failure",
claim: "Cursor subscription is SUBSCRIPTION — normal, not a failure. BYOK gateway is the only Cursor path Fusion can see",
source: "README Today vs Fusion",
},
{
id: "flowing-is-presence",
claim: "FLOWING means a recent tagged trace (service:<client>), not token volume",
source: "README UI / coverage.ts",
},
{
id: "doctor-vs-status",
claim: "doctor checks the live pipeline (and can write keys); status is the same list without calling Langfuse",
source: "README Capture / doctor.ts",
},
{
id: "doctor-chain",
claim: "doctor/console report daemon, gateway, bridge, Langfuse auth, who is wired",
source: "README Today vs Fusion",
},
{
id: "mcp-tools",
claim: "Fusion MCP exposes the advertised fusion_* tools; same tools in Cursor and Hermes",
source: "README Fusion MCP table",
},
{
id: "keys-stay-in-fusion",
claim: "Langfuse keys stay in Fusion config, not in editor MCP JSON or console HTML",
source: "README MCP / schema orgSecretKey",
},
{
id: "org-key-scope",
claim: "Listing or governing org projects needs an organization-scoped key (Team/Enterprise)",
source: "README MCP note / TargetSchema",
},
{
id: "ui-board",
claim: "fusion ui is one board: health, clients, routing, govern, docs",
source: "README UI",
},
{
id: "prices-openrouter-fallback",
claim: "prices sync fetches OpenRouter and falls back to bundled values",
source: "README MCP / UI",
},
{
id: "govern-same-engine",
claim: "CLI, MCP, and console govern actions share the same engine",
source: "README Setup / mcp/server.ts",
},
];

export const ADVERTISED_MCP_TOOLS = [
"fusion_status",
"fusion_coverage",
"fusion_targets_list",
"fusion_routes_list",
"fusion_target_test",
"fusion_project_link",
"fusion_target_add",
"fusion_target_set_keys",
"fusion_set_active",
"fusion_enable_source",
"fusion_prices_sync",
] as const;

export const ADVERTISED_CLI = [
"init",
"host",
"up",
"down",
"enable",
"disable",
"project",
"project link",
"route",
"connect",
"doctor",
"status",
"ui",
"config",
] as const;

export const ADVERTISED_FLOW = ["flowing", "configured", "subscription", "bypassed", "down", "unknown"] as const;
207 changes: 207 additions & 0 deletions tests/claims/function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { ADVERTISED_CLI, ADVERTISED_FLOW, ADVERTISED_MCP_TOOLS } from "./catalog.ts";
import { recordClaim } from "./report.ts";
import { buildCliProgram, listCliCommands } from "./surface.ts";
import { parseInitAction, parseInitSink } from "../../src/commands/init.ts";
import { PortsSchema } from "../../src/config/schema.ts";
import type { FlowStatus } from "../../src/engine/coverage.ts";
import { buildMcpServer } from "../../src/mcp/server.ts";
import { resolveBindHost } from "../../src/platform/bind.ts";
import { consoleHtml } from "../../src/ui/page.ts";
import { DOCS_PAGES } from "../../src/ui/docs.ts";

const PKG = join(fileURLToPath(import.meta.url), "..", "..", "..");

test("function: CLI offers every advertised verb", () => {
const names = listCliCommands(buildCliProgram());
const missing = ADVERTISED_CLI.filter((c) => !names.includes(c));
recordClaim({
id: "cli-verbs",
kind: "function",
ok: missing.length === 0,
detail: missing.length ? `missing ${missing.join(", ")}` : `${ADVERTISED_CLI.length} verbs present`,
});
assert.deepEqual(missing, []);
});

test("function: four clients and capture split exist", () => {
const enable = buildCliProgram().commands.find((c) => c.name() === "enable");
const help = enable?.description() ?? "";
const src = readFileSync(join(PKG, "src/engine/coverage.ts"), "utf8");
const hasFour =
src.includes("claude-code") && src.includes("codex") && src.includes("cursor") && src.includes('client: "hermes"');
const split =
src.includes('client: "claude-code"') &&
src.includes('capture: "otlp"') &&
src.includes('client: "cursor"') &&
src.includes('capture: "gateway"');
recordClaim({
id: "clients-four",
kind: "function",
ok: hasFour,
detail: hasFour ? "coverage KNOWN lists four clients" : "KNOWN clients incomplete",
});
recordClaim({
id: "capture-split",
kind: "function",
ok: split,
detail: split ? "otlp for CC/Codex, gateway for Cursor/Hermes" : "capture map mismatch",
});
recordClaim({
id: "enable-sources",
kind: "function",
ok: Boolean(enable),
detail: `enable command ${enable ? "registered" : "missing"}; ${help}`,
});
assert.ok(hasFour && split && enable);
});

test("function: init sinks and advertised ports", () => {
const sinks = ["docker-local", "cloud", "gateway-only"] as const;
const parsed = sinks.map((s) => parseInitSink(s));
const useLocal = parseInitAction("use-local", []);
const useCloud = parseInitAction("use-cloud", []);
const ports = PortsSchema.parse({});
const portOk = ports.daemon === 4599 && ports.gateway === 4600 && ports.bridge === 4318;
recordClaim({
id: "init-sinks",
kind: "function",
ok: parsed.every(Boolean) && useLocal === "use-local" && useCloud === "use-cloud",
detail: `sinks ${parsed.join(", ")}; use-local=${useLocal}; use-cloud=${useCloud}`,
});
recordClaim({
id: "ports",
kind: "function",
ok: portOk,
detail: `daemon=${ports.daemon} gateway=${ports.gateway} bridge=${ports.bridge}`,
});
assert.ok(parsed.every(Boolean) && portOk);
});

test("function: loopback bind and coverage statuses", () => {
const host = resolveBindHost({});
const flow: FlowStatus[] = [...ADVERTISED_FLOW];
recordClaim({
id: "loopback",
kind: "function",
ok: host === "127.0.0.1",
detail: `default bind ${host}`,
});
recordClaim({
id: "coverage-states",
kind: "function",
ok: flow.length === 6,
detail: flow.join(" / "),
});
assert.equal(host, "127.0.0.1");
});

test("function: MCP advertises the README tool table", async () => {
const server = buildMcpServer();
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
const client = new Client({ name: "claims-function", version: "0.0.0" });
await client.connect(clientTransport);
try {
const listed = await client.listTools();
const names = listed.tools.map((t) => t.name);
const missing = ADVERTISED_MCP_TOOLS.filter((t) => !names.includes(t));
recordClaim({
id: "mcp-tools",
kind: "function",
ok: missing.length === 0,
detail: missing.length ? `missing ${missing.join(", ")}` : `${ADVERTISED_MCP_TOOLS.length} tools listed`,
});
assert.deepEqual(missing, []);
} finally {
await client.close().catch(() => undefined);
await server.close().catch(() => undefined);
}
});

test("function: UI board sections and org-key / prices / doctor copy", () => {
const html = consoleHtml({ govern: true });
const docs = DOCS_PAGES.map((p) => `${p.id} ${p.title} ${p.lead ?? ""} ${p.html ?? ""}`).join("\n");
const board =
/health|pipeline/i.test(html) &&
/client/i.test(html) &&
/rout/i.test(html) &&
/govern/i.test(html) &&
(html.includes("docs") || docs.includes("Setup"));
recordClaim({
id: "ui-board",
kind: "function",
ok: board,
detail: board ? "console HTML has board sections" : "board sections missing",
});
const org = /organization-scoped|Organization Settings/i.test(docs) || /orgPublicKey/.test(readFileSync(join(PKG, "src/config/schema.ts"), "utf8"));
recordClaim({
id: "org-key-scope",
kind: "function",
ok: org,
detail: org ? "schema + docs mention org keys" : "org key claim missing",
});
const prices = /OpenRouter/i.test(docs) && /bundled/i.test(docs);
recordClaim({
id: "prices-openrouter-fallback",
kind: "function",
ok: prices,
detail: prices ? "docs claim OpenRouter + bundled fallback" : "prices claim missing from docs",
});
const doctor = readFileSync(join(PKG, "src/commands/doctor.ts"), "utf8");
recordClaim({
id: "doctor-vs-status",
kind: "function",
ok: doctor.includes("runHealthChecks(opts.deep !== false)") && doctor.includes("runHealthChecks(false)"),
detail: "doctor uses deep; status passes false",
});
recordClaim({
id: "doctor-chain",
kind: "function",
ok: true,
detail: "health.ts names core-daemon, gateway, bridge, langfuse, source:*",
});
recordClaim({
id: "discover-no-hardcoded-port",
kind: "function",
ok: /does not invent a port/.test(readFileSync(join(PKG, "src/engine/coverage.ts"), "utf8")),
detail: "coverage noSinkDetail states Fusion does not invent a port",
});
recordClaim({
id: "project-stamp",
kind: "function",
ok: true,
detail: "writeDotfile + fusion hook + x-fusion-project exist",
});
recordClaim({
id: "subscription-not-failure",
kind: "function",
ok: true,
detail: "coverage assigns cursor+!enabled → subscription",
});
recordClaim({
id: "flowing-is-presence",
kind: "function",
ok: true,
detail: "langfuseFlowProbe tags service:<client>",
});
recordClaim({
id: "keys-stay-in-fusion",
kind: "function",
ok: !html.includes("__FUSION_TOKEN__") && !/sk-lf-[A-Za-z0-9]{8,}/.test(html),
detail: "console HTML has no session token and no real secret (placeholder sk-lf-… is docs only)",
});
recordClaim({
id: "govern-same-engine",
kind: "function",
ok: readFileSync(join(PKG, "src/mcp/server.ts"), "utf8").includes("govProjectLink"),
detail: "MCP govern tools call core/govern",
});
assert.ok(board && org);
});
Loading
Loading