mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
test(mitm): handlers + targets + detection unit tests (F3)
- mitm-handler-base: hookBufferStart returns InterceptedRequest with sanitized headers, extractSourceModel parses body.model, writeError emits JSON with sanitized message (Hard Rule #12). - mitm-handler-<id>: nine per-agent happy-path tests (antigravity, kiro, copilot, codex, cursor, zed, claude-code, open-code) exercise full intercept() with mocked fetch via _mitmHandlerHarness.ts; asserts mapped model is forwarded and AgentBridge correlation headers are present. Trae test confirms intercept() rejects with a structured error. - mitm-targets-resolve: ALL_TARGETS contains 9 entries; resolveTarget is case-insensitive and returns null for unknown hosts. - mitm-targets-route: bypass > target > passthrough precedence validated against representative hostnames. - mitm-detection: DETECTORS covers every AgentId; detectAgent never throws and returns DetectionResult-shaped objects for all probes.
This commit is contained in:
97
tests/unit/_mitmHandlerHarness.ts
Normal file
97
tests/unit/_mitmHandlerHarness.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Test harness for MitmHandlerBase subclasses.
|
||||
*
|
||||
* Mocks `globalThis.fetch` so handlers exercise their full intercept() path
|
||||
* (router round-trip + SSE pipe) without touching the network. Returns the
|
||||
* captured payload, response chunks written to the fake ServerResponse, and
|
||||
* the final status code.
|
||||
*/
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { Readable } from "node:stream";
|
||||
import type { MitmHandlerBase } from "../../src/mitm/handlers/base.ts";
|
||||
|
||||
export interface HarnessResult {
|
||||
fetchCalled: boolean;
|
||||
fetchUrl: string | null;
|
||||
fetchHeaders: Record<string, string>;
|
||||
fetchBody: string;
|
||||
status: number;
|
||||
responseChunks: string[];
|
||||
}
|
||||
|
||||
function fakeReq(headers: Record<string, string> = {}): IncomingMessage {
|
||||
return {
|
||||
method: "POST",
|
||||
url: "/v1/chat/completions",
|
||||
headers: {
|
||||
host: "api.example.com",
|
||||
"user-agent": "ut",
|
||||
...headers,
|
||||
},
|
||||
} as unknown as IncomingMessage;
|
||||
}
|
||||
|
||||
function fakeRes(): { res: ServerResponse; out: HarnessResult } {
|
||||
const out: HarnessResult = {
|
||||
fetchCalled: false,
|
||||
fetchUrl: null,
|
||||
fetchHeaders: {},
|
||||
fetchBody: "",
|
||||
status: 0,
|
||||
responseChunks: [],
|
||||
};
|
||||
let headersSent = false;
|
||||
const res = {
|
||||
get headersSent() {
|
||||
return headersSent;
|
||||
},
|
||||
writeHead(s: number) {
|
||||
out.status = s;
|
||||
headersSent = true;
|
||||
},
|
||||
write(c: Buffer | string) {
|
||||
out.responseChunks.push(typeof c === "string" ? c : c.toString());
|
||||
return true;
|
||||
},
|
||||
end(c?: Buffer | string) {
|
||||
if (c) out.responseChunks.push(typeof c === "string" ? c : c.toString());
|
||||
},
|
||||
} as unknown as ServerResponse;
|
||||
return { res, out };
|
||||
}
|
||||
|
||||
export async function runHandler(
|
||||
handler: MitmHandlerBase,
|
||||
body: unknown,
|
||||
mappedModel: string,
|
||||
opts: {
|
||||
upstreamStatus?: number;
|
||||
upstreamBody?: string;
|
||||
headers?: Record<string, string>;
|
||||
} = {}
|
||||
): Promise<HarnessResult> {
|
||||
const { res, out } = fakeRes();
|
||||
const req = fakeReq(opts.headers);
|
||||
const buf = Buffer.from(typeof body === "string" ? body : JSON.stringify(body));
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (async (url: string, init: RequestInit) => {
|
||||
out.fetchCalled = true;
|
||||
out.fetchUrl = String(url);
|
||||
out.fetchHeaders = (init?.headers ?? {}) as Record<string, string>;
|
||||
out.fetchBody = typeof init?.body === "string" ? init.body : "";
|
||||
const upstreamBody = opts.upstreamBody ?? "data: hello\n\n";
|
||||
const status = opts.upstreamStatus ?? 200;
|
||||
const stream = Readable.toWeb(
|
||||
Readable.from(Buffer.from(upstreamBody))
|
||||
) as unknown as ReadableStream<Uint8Array>;
|
||||
return new Response(stream, { status });
|
||||
}) as unknown as typeof fetch;
|
||||
|
||||
try {
|
||||
await handler.intercept(req, res, buf, mappedModel);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
77
tests/unit/mitm-detection.test.ts
Normal file
77
tests/unit/mitm-detection.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import { detectAgent, DETECTORS } from "../../src/mitm/detection/index.ts";
|
||||
import type { AgentId } from "../../src/mitm/types.ts";
|
||||
|
||||
test("DETECTORS — provides an entry for every AgentId", () => {
|
||||
const ids: AgentId[] = [
|
||||
"antigravity",
|
||||
"kiro",
|
||||
"copilot",
|
||||
"codex",
|
||||
"cursor",
|
||||
"zed",
|
||||
"claude-code",
|
||||
"open-code",
|
||||
"trae",
|
||||
];
|
||||
for (const id of ids) {
|
||||
assert.equal(typeof DETECTORS[id], "function", `missing detector for ${id}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("detectAgent — trae always reports not installed (investigating)", () => {
|
||||
const r = detectAgent("trae");
|
||||
assert.equal(r.installed, false);
|
||||
});
|
||||
|
||||
test("detectAgent — returns installed=true when fs.existsSync hits a known path", () => {
|
||||
// Inject a mock existsSync that returns true for every probed path so the
|
||||
// dispatch path is exercised regardless of host environment.
|
||||
const original = fs.existsSync;
|
||||
let called = 0;
|
||||
(fs as unknown as { existsSync: (p: fs.PathLike) => boolean }).existsSync = () => {
|
||||
called++;
|
||||
return true;
|
||||
};
|
||||
try {
|
||||
// antigravity uses pure existsSync probes — first hit wins.
|
||||
const r = detectAgent("antigravity");
|
||||
assert.equal(r.installed, true);
|
||||
assert.equal(typeof r.path, "string");
|
||||
} finally {
|
||||
(fs as unknown as { existsSync: typeof fs.existsSync }).existsSync = original;
|
||||
}
|
||||
assert.ok(called >= 1);
|
||||
});
|
||||
|
||||
test("detectAgent — antigravity probe returns DetectionResult shape", () => {
|
||||
const r = detectAgent("antigravity");
|
||||
assert.equal(typeof r.installed, "boolean");
|
||||
if (r.installed) assert.equal(typeof r.path, "string");
|
||||
});
|
||||
|
||||
test("detectAgent — gracefully handles thrown detectors", () => {
|
||||
// Unknown id falls through to default false branch.
|
||||
const r = detectAgent("nonexistent" as AgentId);
|
||||
assert.equal(r.installed, false);
|
||||
});
|
||||
|
||||
test("detectAgent — runs all detectors without throwing", () => {
|
||||
const ids: AgentId[] = [
|
||||
"antigravity",
|
||||
"kiro",
|
||||
"copilot",
|
||||
"codex",
|
||||
"cursor",
|
||||
"zed",
|
||||
"claude-code",
|
||||
"open-code",
|
||||
"trae",
|
||||
];
|
||||
for (const id of ids) {
|
||||
const r = detectAgent(id);
|
||||
assert.equal(typeof r.installed, "boolean");
|
||||
}
|
||||
});
|
||||
29
tests/unit/mitm-handler-antigravity.test.ts
Normal file
29
tests/unit/mitm-handler-antigravity.test.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { AntigravityHandler } from "../../src/mitm/handlers/antigravity.ts";
|
||||
import { runHandler } from "./_mitmHandlerHarness.ts";
|
||||
|
||||
test("antigravity handler — forwards to OmniRoute and pipes SSE", async () => {
|
||||
const r = await runHandler(
|
||||
new AntigravityHandler(),
|
||||
{ model: "gpt-4o", messages: [{ role: "user", content: "hi" }] },
|
||||
"claude-3.5-sonnet",
|
||||
{ upstreamBody: "data: hello\n\ndata: world\n\n" }
|
||||
);
|
||||
assert.ok(r.fetchCalled);
|
||||
assert.equal(r.status, 200);
|
||||
assert.ok(r.responseChunks.join("").includes("hello"));
|
||||
});
|
||||
|
||||
test("antigravity handler — propagates upstream failure as 500", async () => {
|
||||
const r = await runHandler(
|
||||
new AntigravityHandler(),
|
||||
{ model: "gpt-4o" },
|
||||
"claude-3.5-sonnet",
|
||||
{ upstreamStatus: 500, upstreamBody: "boom" }
|
||||
);
|
||||
assert.equal(r.status, 500);
|
||||
const body = r.responseChunks.join("");
|
||||
// Error must NOT include raw stack trace (Hard Rule #12 sanitization).
|
||||
assert.ok(!body.includes("at /"));
|
||||
});
|
||||
115
tests/unit/mitm-handler-base.test.ts
Normal file
115
tests/unit/mitm-handler-base.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import type { AgentId } from "../../src/mitm/types.ts";
|
||||
import { MitmHandlerBase } from "../../src/mitm/handlers/base.ts";
|
||||
|
||||
// Concrete subclass exposing the protected helpers for testing.
|
||||
class TestHandler extends MitmHandlerBase {
|
||||
readonly agentId: AgentId = "antigravity";
|
||||
|
||||
async intercept(): Promise<void> {
|
||||
// Not exercised in this suite.
|
||||
}
|
||||
|
||||
publicExtract(buf: Buffer): string | null {
|
||||
return this.extractSourceModel(buf);
|
||||
}
|
||||
|
||||
async publicHookStart(
|
||||
req: IncomingMessage,
|
||||
body: Buffer,
|
||||
mapped: string
|
||||
): Promise<ReturnType<MitmHandlerBase["hookBufferStart"]>> {
|
||||
return this.hookBufferStart(req, body, mapped);
|
||||
}
|
||||
}
|
||||
|
||||
function fakeReq(headers: Record<string, string> = {}): IncomingMessage {
|
||||
return {
|
||||
method: "POST",
|
||||
url: "/v1/chat/completions",
|
||||
headers: {
|
||||
host: "api.example.com",
|
||||
"user-agent": "ut",
|
||||
// 50-char opaque token — long enough to be masked by LONG_TOKEN rule.
|
||||
authorization: "Bearer abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKL",
|
||||
...headers,
|
||||
},
|
||||
} as unknown as IncomingMessage;
|
||||
}
|
||||
|
||||
test("base.extractSourceModel — reads body.model from JSON", () => {
|
||||
const h = new TestHandler();
|
||||
const buf = Buffer.from(JSON.stringify({ model: "gpt-4o", messages: [] }));
|
||||
assert.equal(h.publicExtract(buf), "gpt-4o");
|
||||
});
|
||||
|
||||
test("base.extractSourceModel — non-JSON body returns null", () => {
|
||||
const h = new TestHandler();
|
||||
assert.equal(h.publicExtract(Buffer.from("not json")), null);
|
||||
});
|
||||
|
||||
test("base.extractSourceModel — missing model field returns null", () => {
|
||||
const h = new TestHandler();
|
||||
const buf = Buffer.from(JSON.stringify({ messages: [] }));
|
||||
assert.equal(h.publicExtract(buf), null);
|
||||
});
|
||||
|
||||
test("base.hookBufferStart — local stub returns InterceptedRequest with sanitized headers", async () => {
|
||||
const h = new TestHandler();
|
||||
const req = fakeReq();
|
||||
const body = Buffer.from(JSON.stringify({ model: "gpt-4o" }));
|
||||
const r = await h.publicHookStart(req, body, "claude-3.5-sonnet");
|
||||
|
||||
assert.equal(r.agent, "antigravity");
|
||||
assert.equal(r.source, "agent-bridge");
|
||||
assert.equal(r.mappedModel, "claude-3.5-sonnet");
|
||||
assert.equal(r.sourceModel, "gpt-4o");
|
||||
assert.equal(r.host, "api.example.com");
|
||||
assert.equal(r.status, "in-flight");
|
||||
// sanitizeHeaders should mask the long opaque token in `authorization`.
|
||||
const auth = r.requestHeaders["authorization"];
|
||||
assert.ok(
|
||||
!auth ||
|
||||
(typeof auth === "string" &&
|
||||
!auth.includes("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKL")),
|
||||
`sanitizeHeaders failed to mask authorization: ${JSON.stringify(auth)}`
|
||||
);
|
||||
});
|
||||
|
||||
test("base.hookBufferStart — body is captured (default shouldCaptureBody=true)", async () => {
|
||||
const h = new TestHandler();
|
||||
const req = fakeReq();
|
||||
const body = Buffer.from(JSON.stringify({ model: "gpt-4o" }));
|
||||
const r = await h.publicHookStart(req, body, "x");
|
||||
assert.equal(r.requestSize, body.length);
|
||||
assert.ok(typeof r.requestBody === "string");
|
||||
});
|
||||
|
||||
test("base.writeError — writes sanitized JSON error body", async () => {
|
||||
const h = new TestHandler();
|
||||
let status = 0;
|
||||
let payload = "";
|
||||
const res = {
|
||||
headersSent: false,
|
||||
writeHead(s: number) {
|
||||
status = s;
|
||||
},
|
||||
end(p: string) {
|
||||
payload = p;
|
||||
},
|
||||
} as unknown as ServerResponse;
|
||||
|
||||
// Calling a protected method through `any` to avoid leaking it on
|
||||
// the production surface area.
|
||||
await (h as unknown as { writeError: MitmHandlerBase["writeError"] }).writeError(
|
||||
res,
|
||||
new Error("boom"),
|
||||
502
|
||||
);
|
||||
assert.equal(status, 502);
|
||||
const obj = JSON.parse(payload);
|
||||
assert.equal(obj.error.type, "mitm_error");
|
||||
assert.ok(typeof obj.error.message === "string");
|
||||
});
|
||||
17
tests/unit/mitm-handler-claudeCode.test.ts
Normal file
17
tests/unit/mitm-handler-claudeCode.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { ClaudeCodeHandler } from "../../src/mitm/handlers/claudeCode.ts";
|
||||
import { runHandler } from "./_mitmHandlerHarness.ts";
|
||||
|
||||
test("claude-code handler — happy path forwards to /v1/messages", async () => {
|
||||
const r = await runHandler(
|
||||
new ClaudeCodeHandler(),
|
||||
{ model: "claude-3.5-sonnet", messages: [] },
|
||||
"claude-opus-4.5"
|
||||
);
|
||||
assert.ok(r.fetchCalled);
|
||||
assert.equal(r.status, 200);
|
||||
assert.ok(r.fetchUrl?.endsWith("/v1/messages"));
|
||||
const sent = JSON.parse(r.fetchBody);
|
||||
assert.equal(sent.model, "claude-opus-4.5");
|
||||
});
|
||||
17
tests/unit/mitm-handler-codex.test.ts
Normal file
17
tests/unit/mitm-handler-codex.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { CodexHandler } from "../../src/mitm/handlers/codex.ts";
|
||||
import { runHandler } from "./_mitmHandlerHarness.ts";
|
||||
|
||||
test("codex handler — forwards Chat Completions payload via OmniRoute", async () => {
|
||||
const r = await runHandler(
|
||||
new CodexHandler(),
|
||||
{ model: "gpt-4.1", messages: [] },
|
||||
"gpt-4o-mini"
|
||||
);
|
||||
assert.ok(r.fetchCalled);
|
||||
assert.equal(r.status, 200);
|
||||
assert.ok(r.fetchUrl?.endsWith("/v1/chat/completions"));
|
||||
const sent = JSON.parse(r.fetchBody);
|
||||
assert.equal(sent.model, "gpt-4o-mini");
|
||||
});
|
||||
21
tests/unit/mitm-handler-copilot.test.ts
Normal file
21
tests/unit/mitm-handler-copilot.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { CopilotHandler } from "../../src/mitm/handlers/copilot.ts";
|
||||
import { runHandler } from "./_mitmHandlerHarness.ts";
|
||||
|
||||
test("copilot handler — rewrites model and forwards to /v1/chat/completions", async () => {
|
||||
const r = await runHandler(
|
||||
new CopilotHandler(),
|
||||
{ model: "gpt-4o", messages: [] },
|
||||
"claude-3.5-sonnet"
|
||||
);
|
||||
assert.ok(r.fetchCalled);
|
||||
assert.equal(r.status, 200);
|
||||
assert.ok(r.fetchUrl?.endsWith("/v1/chat/completions"));
|
||||
const sent = JSON.parse(r.fetchBody);
|
||||
assert.equal(sent.model, "claude-3.5-sonnet");
|
||||
// AgentBridge correlation headers must be present.
|
||||
const headers = r.fetchHeaders as Record<string, string>;
|
||||
assert.equal(headers["x-omniroute-source"], "agent-bridge");
|
||||
assert.equal(headers["x-omniroute-agent"], "copilot");
|
||||
});
|
||||
16
tests/unit/mitm-handler-cursor.test.ts
Normal file
16
tests/unit/mitm-handler-cursor.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { CursorHandler } from "../../src/mitm/handlers/cursor.ts";
|
||||
import { runHandler } from "./_mitmHandlerHarness.ts";
|
||||
|
||||
test("cursor handler — happy path forwards mapped model", async () => {
|
||||
const r = await runHandler(
|
||||
new CursorHandler(),
|
||||
{ model: "claude-sonnet-4.5", messages: [] },
|
||||
"gpt-4o"
|
||||
);
|
||||
assert.ok(r.fetchCalled);
|
||||
assert.equal(r.status, 200);
|
||||
const sent = JSON.parse(r.fetchBody);
|
||||
assert.equal(sent.model, "gpt-4o");
|
||||
});
|
||||
20
tests/unit/mitm-handler-kiro.test.ts
Normal file
20
tests/unit/mitm-handler-kiro.test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { KiroHandler } from "../../src/mitm/handlers/kiro.ts";
|
||||
import { runHandler } from "./_mitmHandlerHarness.ts";
|
||||
|
||||
test("kiro handler — forwards Anthropic-style body to OmniRoute /v1/messages", async () => {
|
||||
const r = await runHandler(
|
||||
new KiroHandler(),
|
||||
{ model: "claude-3.5-sonnet", messages: [{ role: "user", content: "hi" }] },
|
||||
"claude-sonnet-4.5",
|
||||
{ upstreamBody: "event: message_start\n\n" }
|
||||
);
|
||||
assert.ok(r.fetchCalled);
|
||||
assert.equal(r.status, 200);
|
||||
// Router URL must point at /v1/messages for the Anthropic path.
|
||||
assert.ok(r.fetchUrl?.endsWith("/v1/messages"));
|
||||
// Body must have been rewritten with mapped model.
|
||||
const sent = JSON.parse(r.fetchBody);
|
||||
assert.equal(sent.model, "claude-sonnet-4.5");
|
||||
});
|
||||
16
tests/unit/mitm-handler-openCode.test.ts
Normal file
16
tests/unit/mitm-handler-openCode.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { OpenCodeHandler } from "../../src/mitm/handlers/openCode.ts";
|
||||
import { runHandler } from "./_mitmHandlerHarness.ts";
|
||||
|
||||
test("open-code handler — happy path forwards Chat Completions payload", async () => {
|
||||
const r = await runHandler(
|
||||
new OpenCodeHandler(),
|
||||
{ model: "gpt-4o", messages: [] },
|
||||
"claude-3.5-sonnet"
|
||||
);
|
||||
assert.ok(r.fetchCalled);
|
||||
assert.equal(r.status, 200);
|
||||
const sent = JSON.parse(r.fetchBody);
|
||||
assert.equal(sent.model, "claude-3.5-sonnet");
|
||||
});
|
||||
23
tests/unit/mitm-handler-trae.test.ts
Normal file
23
tests/unit/mitm-handler-trae.test.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { TraeHandler } from "../../src/mitm/handlers/trae.ts";
|
||||
|
||||
test("trae handler — intercept throws structured error (viability=investigating)", async () => {
|
||||
const h = new TraeHandler();
|
||||
const req = {
|
||||
method: "POST",
|
||||
url: "/x",
|
||||
headers: { host: "trae.invalid" },
|
||||
} as unknown as IncomingMessage;
|
||||
const res = {
|
||||
headersSent: false,
|
||||
writeHead() {},
|
||||
end() {},
|
||||
} as unknown as ServerResponse;
|
||||
|
||||
await assert.rejects(
|
||||
() => h.intercept(req, res, Buffer.from("{}"), "gpt-4o"),
|
||||
/investigation|invalid|not.*implement/i
|
||||
);
|
||||
});
|
||||
16
tests/unit/mitm-handler-zed.test.ts
Normal file
16
tests/unit/mitm-handler-zed.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { ZedHandler } from "../../src/mitm/handlers/zed.ts";
|
||||
import { runHandler } from "./_mitmHandlerHarness.ts";
|
||||
|
||||
test("zed handler — happy path forwards Chat Completions payload", async () => {
|
||||
const r = await runHandler(
|
||||
new ZedHandler(),
|
||||
{ model: "gpt-4o", messages: [] },
|
||||
"claude-3.5-sonnet"
|
||||
);
|
||||
assert.ok(r.fetchCalled);
|
||||
assert.equal(r.status, 200);
|
||||
const sent = JSON.parse(r.fetchBody);
|
||||
assert.equal(sent.model, "claude-3.5-sonnet");
|
||||
});
|
||||
53
tests/unit/mitm-targets-resolve.test.ts
Normal file
53
tests/unit/mitm-targets-resolve.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { ALL_TARGETS, resolveTarget } from "../../src/mitm/targets/index.ts";
|
||||
|
||||
test("resolveTarget — antigravity host resolves to antigravity target", () => {
|
||||
const t = resolveTarget("cloudcode-pa.googleapis.com");
|
||||
assert.ok(t);
|
||||
assert.equal(t?.id, "antigravity");
|
||||
});
|
||||
|
||||
test("resolveTarget — kiro host resolves to kiro target (Anthropic)", () => {
|
||||
// api.anthropic.com is shared by kiro and claude-code; the first match wins.
|
||||
const t = resolveTarget("api.anthropic.com");
|
||||
assert.ok(t);
|
||||
assert.ok(t?.id === "kiro" || t?.id === "claude-code");
|
||||
});
|
||||
|
||||
test("resolveTarget — copilot host resolves", () => {
|
||||
assert.equal(resolveTarget("api.githubcopilot.com")?.id, "copilot");
|
||||
});
|
||||
|
||||
test("resolveTarget — cursor host resolves", () => {
|
||||
assert.equal(resolveTarget("api2.cursor.sh")?.id, "cursor");
|
||||
});
|
||||
|
||||
test("resolveTarget — case-insensitive match", () => {
|
||||
assert.equal(resolveTarget("API.ZED.DEV")?.id, "zed");
|
||||
});
|
||||
|
||||
test("resolveTarget — unknown host returns null", () => {
|
||||
assert.equal(resolveTarget("example.com"), null);
|
||||
assert.equal(resolveTarget(""), null);
|
||||
});
|
||||
|
||||
test("ALL_TARGETS — registers exactly nine targets", () => {
|
||||
assert.equal(ALL_TARGETS.length, 9);
|
||||
const ids = new Set(ALL_TARGETS.map((t) => t.id));
|
||||
assert.equal(ids.size, 9);
|
||||
assert.ok(ids.has("antigravity"));
|
||||
assert.ok(ids.has("kiro"));
|
||||
assert.ok(ids.has("copilot"));
|
||||
assert.ok(ids.has("codex"));
|
||||
assert.ok(ids.has("cursor"));
|
||||
assert.ok(ids.has("zed"));
|
||||
assert.ok(ids.has("claude-code"));
|
||||
assert.ok(ids.has("open-code"));
|
||||
assert.ok(ids.has("trae"));
|
||||
});
|
||||
|
||||
test("ALL_TARGETS — trae is marked viability=investigating", () => {
|
||||
const trae = ALL_TARGETS.find((t) => t.id === "trae");
|
||||
assert.equal(trae?.viability, "investigating");
|
||||
});
|
||||
37
tests/unit/mitm-targets-route.test.ts
Normal file
37
tests/unit/mitm-targets-route.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { routeConnection } from "../../src/mitm/targets/index.ts";
|
||||
|
||||
test("routeConnection — default bypass (bank) wins over target match", () => {
|
||||
const r = routeConnection("my.bank.example", []);
|
||||
assert.equal(r.kind, "bypass");
|
||||
});
|
||||
|
||||
test("routeConnection — user bypass glob beats target", () => {
|
||||
const r = routeConnection("api.githubcopilot.com", ["*githubcopilot*"]);
|
||||
assert.equal(r.kind, "bypass");
|
||||
});
|
||||
|
||||
test("routeConnection — known target returns target route", () => {
|
||||
const r = routeConnection("api.githubcopilot.com", []);
|
||||
assert.equal(r.kind, "target");
|
||||
if (r.kind === "target") assert.equal(r.target.id, "copilot");
|
||||
});
|
||||
|
||||
test("routeConnection — unknown host returns passthrough", () => {
|
||||
const r = routeConnection("example.com", []);
|
||||
assert.equal(r.kind, "passthrough");
|
||||
});
|
||||
|
||||
test("routeConnection — empty hostname returns passthrough", () => {
|
||||
const r = routeConnection("", []);
|
||||
assert.equal(r.kind, "passthrough");
|
||||
});
|
||||
|
||||
test("routeConnection — precedence: bypass > target > passthrough", () => {
|
||||
// Default bypass (bank) takes precedence even if we add the host to the
|
||||
// copilot target hypothetically — here we just exercise the three branches.
|
||||
assert.equal(routeConnection("acme.bank.com", []).kind, "bypass");
|
||||
assert.equal(routeConnection("api.zed.dev", []).kind, "target");
|
||||
assert.equal(routeConnection("unrelated.example", []).kind, "passthrough");
|
||||
});
|
||||
Reference in New Issue
Block a user