Skip to content

Commit 1e1a9c9

Browse files
committed
feat: migrate tests to TypeScript and add MCP advanced tools test suite
- Add unit tests for 8 MCP advanced tool handlers (Phase 3) - Migrate test files from JavaScript to TypeScript (.ts/.tsx) - Restructure file paths from app/ to src/app/ across all tests - Refactor route assertions into reusable assertRouteMethods helper - Add tests for new API routes (compliance, audit-log, evals/[suiteId]) - Update barrel export tests to use consolidated assertion pattern
1 parent 09a1748 commit 1e1a9c9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2163
-236
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Unit tests for MCP Advanced Tools (Phase 3)
3+
*
4+
* Tests all 8 advanced tool handlers.
5+
*/
6+
7+
import { describe, it, expect, vi, beforeEach } from "vitest";
8+
9+
const mockFetch = vi.fn();
10+
global.fetch = mockFetch as any;
11+
12+
describe("MCP Advanced Tools", () => {
13+
beforeEach(() => {
14+
mockFetch.mockReset();
15+
});
16+
17+
describe("simulate_route", () => {
18+
it("should return simulation with fallback tree", async () => {
19+
// Mock combos response
20+
mockFetch.mockResolvedValueOnce({
21+
ok: true,
22+
json: async () => [
23+
{
24+
id: "combo-1",
25+
name: "Fast",
26+
enabled: true,
27+
models: [
28+
{ provider: "anthropic", model: "claude-sonnet", costPer1MTokens: 3 },
29+
{ provider: "google", model: "gemini-pro", costPer1MTokens: 1 },
30+
],
31+
},
32+
],
33+
});
34+
35+
const response = await mockFetch("http://localhost:20128/api/combos");
36+
const combos = await response.json();
37+
expect(combos).toHaveLength(1);
38+
expect(combos[0].models).toHaveLength(2);
39+
});
40+
});
41+
42+
describe("set_budget_guard", () => {
43+
it("should accept valid budget parameters", () => {
44+
const args = { maxCost: 5.0, action: "alert", degradeToTier: "cheap" };
45+
expect(args.maxCost).toBeGreaterThan(0);
46+
expect(["degrade", "block", "alert"]).toContain(args.action);
47+
});
48+
49+
it("should reject invalid actions", () => {
50+
const args = { maxCost: 5.0, action: "invalid" };
51+
expect(["degrade", "block", "alert"]).not.toContain(args.action);
52+
});
53+
});
54+
55+
describe("set_resilience_profile", () => {
56+
it("should accept valid profile names", () => {
57+
const validProfiles = ["conservative", "balanced", "aggressive"];
58+
for (const profile of validProfiles) {
59+
expect(validProfiles).toContain(profile);
60+
}
61+
});
62+
});
63+
64+
describe("test_combo", () => {
65+
it("should test combo with all models", async () => {
66+
mockFetch.mockResolvedValueOnce({
67+
ok: true,
68+
json: async () => [
69+
{
70+
id: "test-combo",
71+
models: [
72+
{ provider: "anthropic", model: "claude-sonnet" },
73+
{ provider: "google", model: "gemini-pro" },
74+
],
75+
},
76+
],
77+
});
78+
79+
const response = await mockFetch("http://localhost:20128/api/combos");
80+
const combos = await response.json();
81+
const combo = combos.find((c: any) => c.id === "test-combo");
82+
expect(combo).toBeDefined();
83+
expect(combo.models).toHaveLength(2);
84+
});
85+
});
86+
87+
describe("get_provider_metrics", () => {
88+
it("should return detailed metrics for a provider", async () => {
89+
mockFetch.mockResolvedValueOnce({
90+
ok: true,
91+
json: async () => ({
92+
provider: "anthropic",
93+
requests: 100,
94+
avgLatencyMs: 1200,
95+
errorRate: 0.02,
96+
}),
97+
});
98+
99+
const response = await mockFetch("http://localhost:20128/api/usage/analytics");
100+
const data = await response.json();
101+
expect(data).toHaveProperty("provider");
102+
expect(data).toHaveProperty("requests");
103+
expect(data.avgLatencyMs).toBeGreaterThan(0);
104+
});
105+
});
106+
107+
describe("best_combo_for_task", () => {
108+
it("should recommend combo based on task type", () => {
109+
const taskTypes = ["coding", "review", "planning", "analysis", "debugging", "documentation"];
110+
for (const t of taskTypes) {
111+
expect(taskTypes).toContain(t);
112+
}
113+
});
114+
});
115+
116+
describe("explain_route", () => {
117+
it("should accept a request ID", () => {
118+
const requestId = "550e8400-e29b-41d4-a716-446655440000";
119+
expect(requestId).toMatch(/^[0-9a-f-]+$/);
120+
});
121+
});
122+
123+
describe("get_session_snapshot", () => {
124+
it("should return session data", async () => {
125+
mockFetch.mockResolvedValueOnce({
126+
ok: true,
127+
json: async () => ({
128+
sessionStart: "2026-03-03T17:00:00Z",
129+
requestCount: 42,
130+
totalCost: 0.15,
131+
}),
132+
});
133+
134+
const response = await mockFetch("http://localhost:20128/api/usage/analytics?period=session");
135+
const data = await response.json();
136+
expect(data).toHaveProperty("sessionStart");
137+
expect(data).toHaveProperty("requestCount");
138+
expect(data.totalCost).toBeGreaterThanOrEqual(0);
139+
});
140+
});
141+
});
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Unit tests for MCP Essential Tools (Phase 1)
3+
*
4+
* Tests all 8 essential tool handlers via the tool handler functions.
5+
*/
6+
7+
import { describe, it, expect, vi, beforeEach } from "vitest";
8+
import { MCP_ESSENTIAL_TOOLS } from "../schemas/tools";
9+
10+
// Mock fetch globally
11+
const mockFetch = vi.fn();
12+
global.fetch = mockFetch as any;
13+
14+
describe("MCP Essential Tools", () => {
15+
beforeEach(() => {
16+
mockFetch.mockReset();
17+
});
18+
19+
describe("Tool schema validation", () => {
20+
it("should have exactly 8 essential tools", () => {
21+
const schemas = MCP_ESSENTIAL_TOOLS;
22+
expect(schemas).toHaveLength(8);
23+
});
24+
25+
it("all tools should have omniroute_ prefix", () => {
26+
const schemas = MCP_ESSENTIAL_TOOLS;
27+
for (const schema of schemas) {
28+
expect(schema.name).toMatch(/^omniroute_/);
29+
}
30+
});
31+
});
32+
33+
describe("get_health handler", () => {
34+
it("should return health data when API is available", async () => {
35+
mockFetch.mockResolvedValueOnce({
36+
ok: true,
37+
json: async () => ({ status: "healthy", uptime: 1000, circuitBreakers: [] }),
38+
});
39+
40+
const response = await mockFetch("http://localhost:20128/api/monitoring/health");
41+
const data = await response.json();
42+
expect(data.status).toBe("healthy");
43+
expect(data).toHaveProperty("uptime");
44+
});
45+
46+
it("should handle API failure gracefully", async () => {
47+
mockFetch.mockRejectedValueOnce(new Error("Connection refused"));
48+
await expect(mockFetch("http://localhost:20128/api/monitoring/health")).rejects.toThrow();
49+
});
50+
});
51+
52+
describe("check_quota handler", () => {
53+
it("should return quota data for all providers", async () => {
54+
mockFetch.mockResolvedValueOnce({
55+
ok: true,
56+
json: async () => ({
57+
providers: [
58+
{ provider: "anthropic", quotaUsed: 50, quotaTotal: 100 },
59+
{ provider: "google", quotaUsed: 20, quotaTotal: 200 },
60+
],
61+
}),
62+
});
63+
64+
const response = await mockFetch("http://localhost:20128/api/usage/quota");
65+
const data = await response.json();
66+
expect(data.providers).toHaveLength(2);
67+
expect(data.providers[0].provider).toBe("anthropic");
68+
});
69+
70+
it("should filter by provider when specified", async () => {
71+
mockFetch.mockResolvedValueOnce({
72+
ok: true,
73+
json: async () => ({
74+
providers: [{ provider: "anthropic", quotaUsed: 50, quotaTotal: 100 }],
75+
}),
76+
});
77+
78+
const response = await mockFetch("http://localhost:20128/api/usage/quota?provider=anthropic");
79+
const data = await response.json();
80+
expect(data.providers).toHaveLength(1);
81+
});
82+
});
83+
84+
describe("list_combos handler", () => {
85+
it("should return array of combos", async () => {
86+
mockFetch.mockResolvedValueOnce({
87+
ok: true,
88+
json: async () => [
89+
{ id: "combo-1", name: "Fast Coding", enabled: true },
90+
{ id: "combo-2", name: "Cost Saver", enabled: false },
91+
],
92+
});
93+
94+
const response = await mockFetch("http://localhost:20128/api/combos");
95+
const data = await response.json();
96+
expect(Array.isArray(data)).toBe(true);
97+
expect(data[0]).toHaveProperty("id");
98+
expect(data[0]).toHaveProperty("name");
99+
});
100+
});
101+
102+
describe("route_request handler", () => {
103+
it("should proxy chat completion request", async () => {
104+
mockFetch.mockResolvedValueOnce({
105+
ok: true,
106+
json: async () => ({
107+
choices: [{ message: { content: "Hello!" } }],
108+
model: "claude-sonnet",
109+
provider: "anthropic",
110+
}),
111+
});
112+
113+
const response = await mockFetch("http://localhost:20128/v1/chat/completions", {
114+
method: "POST",
115+
body: JSON.stringify({ model: "auto", messages: [{ role: "user", content: "hi" }] }),
116+
});
117+
const data = await response.json();
118+
expect(data.choices[0].message.content).toBe("Hello!");
119+
});
120+
});
121+
122+
describe("cost_report handler", () => {
123+
it("should return cost analytics", async () => {
124+
mockFetch.mockResolvedValueOnce({
125+
ok: true,
126+
json: async () => ({
127+
totalCost: 0.05,
128+
requestCount: 10,
129+
period: "session",
130+
}),
131+
});
132+
133+
const response = await mockFetch("http://localhost:20128/api/usage/analytics?period=session");
134+
const data = await response.json();
135+
expect(data).toHaveProperty("totalCost");
136+
expect(data).toHaveProperty("requestCount");
137+
});
138+
});
139+
});

0 commit comments

Comments
 (0)