Spaces:
Runtime error
Runtime error
| /** | |
| * E2E Test Suite β OmniRoute Ecosystem | |
| * | |
| * 6 scenarios covering MCP, A2A, Auto-Combo, Extension, Stress, and Security. | |
| * Run with: npm run test:ecosystem | |
| */ | |
| import { describe, it, expect, beforeAll } from "vitest"; | |
| const BASE_URL = process.env.OMNIROUTE_BASE_URL || "http://localhost:20128"; | |
| const API_KEY = process.env.OMNIROUTE_API_KEY || ""; | |
| const REQUEST_TIMEOUT_MS = Number(process.env.ECOSYSTEM_REQUEST_TIMEOUT_MS || 30000); | |
| const TEST_TIMEOUT_MS = Number(process.env.ECOSYSTEM_TEST_TIMEOUT_MS || 30000); | |
| const STRESS_TIMEOUT_MS = Number(process.env.ECOSYSTEM_STRESS_TIMEOUT_MS || 45000); | |
| function itCase(name: string, fn: () => Promise<void> | void) { | |
| return it(name, fn, TEST_TIMEOUT_MS); | |
| } | |
| function itStress(name: string, fn: () => Promise<void> | void) { | |
| return it(name, fn, STRESS_TIMEOUT_MS); | |
| } | |
| async function apiFetch(path: string, options?: RequestInit) { | |
| return fetch(`${BASE_URL}${path}`, { | |
| ...options, | |
| headers: { | |
| "Content-Type": "application/json", | |
| ...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}), | |
| ...(options?.headers || {}), | |
| }, | |
| signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), | |
| }); | |
| } | |
| // βββ Scenario 1: MCP Server Complete βββββββββββββββββββββββββββββ | |
| describe("E2E: MCP Server (16 tools)", () => { | |
| itCase("should respond to health check", async () => { | |
| const res = await apiFetch("/api/monitoring/health"); | |
| expect(res.ok).toBe(true); | |
| const data = (await res.json()) as any; | |
| expect(data).toHaveProperty("status"); | |
| }); | |
| itCase("should list combos", async () => { | |
| const res = await apiFetch("/api/combos"); | |
| expect(res.ok).toBe(true); | |
| const data = (await res.json()) as any; | |
| expect(Array.isArray(data?.combos)).toBe(true); | |
| }); | |
| itCase("should return quota data", async () => { | |
| const res = await apiFetch("/api/usage/quota"); | |
| expect(res.ok).toBe(true); | |
| const data = (await res.json()) as any; | |
| expect(Array.isArray(data?.providers)).toBe(true); | |
| expect(data).toHaveProperty("meta"); | |
| }); | |
| itCase("should return usage analytics", async () => { | |
| const res = await apiFetch("/api/usage/analytics?period=session"); | |
| expect(res.ok).toBe(true); | |
| }); | |
| itCase("should return model catalog", async () => { | |
| const res = await apiFetch("/api/models"); | |
| expect(res.ok).toBe(true); | |
| }); | |
| }); | |
| // βββ Scenario 1B: Quota Contract βββββββββββββββββββββββββββββ | |
| describe("E2E: Quota Contract (/api/usage/quota)", () => { | |
| itCase("should return normalized quota response shape", async () => { | |
| const res = await apiFetch("/api/usage/quota"); | |
| expect(res.ok).toBe(true); | |
| const data = (await res.json()) as any; | |
| expect(Array.isArray(data.providers)).toBe(true); | |
| expect(data).toHaveProperty("meta"); | |
| expect(typeof data.meta.generatedAt).toBe("string"); | |
| expect(typeof data.meta.totalProviders).toBe("number"); | |
| if (data.providers.length > 0) { | |
| const p = data.providers[0]; | |
| expect(typeof p.name).toBe("string"); | |
| expect(typeof p.provider).toBe("string"); | |
| expect(typeof p.connectionId).toBe("string"); | |
| expect(typeof p.quotaUsed).toBe("number"); | |
| expect(typeof p.percentRemaining).toBe("number"); | |
| expect(p.percentRemaining).toBeGreaterThanOrEqual(0); | |
| expect(p.percentRemaining).toBeLessThanOrEqual(100); | |
| expect(["valid", "expiring", "expired", "refreshing"]).toContain(p.tokenStatus); | |
| } | |
| }); | |
| itCase("should filter quota by provider", async () => { | |
| const allRes = await apiFetch("/api/usage/quota"); | |
| expect(allRes.ok).toBe(true); | |
| const allData = (await allRes.json()) as any; | |
| if (!Array.isArray(allData.providers) || allData.providers.length === 0) return; | |
| const provider = allData.providers[0].provider; | |
| const filteredRes = await apiFetch(`/api/usage/quota?provider=${encodeURIComponent(provider)}`); | |
| expect(filteredRes.ok).toBe(true); | |
| const filteredData = (await filteredRes.json()) as any; | |
| expect(filteredData.meta.filters.provider).toBe(provider); | |
| expect(Array.isArray(filteredData.providers)).toBe(true); | |
| expect(filteredData.providers.every((p: any) => p.provider === provider)).toBe(true); | |
| }); | |
| itCase("should filter quota by connectionId", async () => { | |
| const allRes = await apiFetch("/api/usage/quota"); | |
| expect(allRes.ok).toBe(true); | |
| const allData = (await allRes.json()) as any; | |
| if (!Array.isArray(allData.providers) || allData.providers.length === 0) return; | |
| const connectionId = allData.providers[0].connectionId; | |
| const filteredRes = await apiFetch( | |
| `/api/usage/quota?connectionId=${encodeURIComponent(connectionId)}` | |
| ); | |
| expect(filteredRes.ok).toBe(true); | |
| const filteredData = (await filteredRes.json()) as any; | |
| expect(filteredData.meta.filters.connectionId).toBe(connectionId); | |
| expect(Array.isArray(filteredData.providers)).toBe(true); | |
| expect(filteredData.providers.every((p: any) => p.connectionId === connectionId)).toBe(true); | |
| }); | |
| }); | |
| // βββ Scenario 2: A2A Server Complete βββββββββββββββββββββββββββββ | |
| describe("E2E: A2A Server (lifecycle)", () => { | |
| beforeAll(async () => { | |
| await apiFetch("/api/settings", { | |
| method: "PATCH", | |
| body: JSON.stringify({ a2aEnabled: true }), | |
| }); | |
| }); | |
| itCase("should serve Agent Card", async () => { | |
| const res = await apiFetch("/.well-known/agent.json"); | |
| expect(res.ok).toBe(true); | |
| const card = (await res.json()) as any; | |
| expect(card).toHaveProperty("name"); | |
| expect(card).toHaveProperty("skills"); | |
| expect(card).toHaveProperty("version"); | |
| expect(card.capabilities).toHaveProperty("streaming"); | |
| }); | |
| itCase("should accept message/send via JSON-RPC", async () => { | |
| const res = await apiFetch("/a2a", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| jsonrpc: "2.0", | |
| id: "e2e-1", | |
| method: "message/send", | |
| params: { | |
| skill: "quota-management", | |
| messages: [{ role: "user", content: "show quota ranking" }], | |
| }, | |
| }), | |
| }); | |
| expect(res.ok).toBe(true); | |
| const data = (await res.json()) as any; | |
| expect(data).toHaveProperty("result"); | |
| expect(data.result.task).toHaveProperty("id"); | |
| expect(data.result.task).toHaveProperty("state"); | |
| }); | |
| itCase("should reject invalid JSON-RPC method", async () => { | |
| const res = await apiFetch("/a2a", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| jsonrpc: "2.0", | |
| id: "e2e-2", | |
| method: "invalid/method", | |
| params: {}, | |
| }), | |
| }); | |
| const data = (await res.json()) as any; | |
| expect(data).toHaveProperty("error"); | |
| expect(data.error.code).toBe(-32601); | |
| }); | |
| }); | |
| // βββ Scenario 3: Auto-Combo βββββββββββββββββββββββββββββββββββββ | |
| describe("E2E: Auto-Combo (routing + self-healing)", () => { | |
| itCase("should create auto-combo", async () => { | |
| const res = await apiFetch("/api/combos", { | |
| method: "POST", | |
| body: JSON.stringify({ | |
| name: `e2e-auto-test-${Date.now()}`, | |
| strategy: "auto", | |
| models: [{ model: "gpt-4" }], | |
| config: { | |
| candidatePool: ["anthropic", "google"], | |
| modePack: "ship-fast", | |
| }, | |
| }), | |
| }); | |
| if (!res.ok) console.error("POST /api/combos failed:", await res.text()); | |
| expect(res.ok).toBe(true); | |
| }); | |
| itCase("should list auto-combos", async () => { | |
| const res = await apiFetch("/api/combos"); | |
| if (!res.ok) console.error("GET /api/combos failed:", await res.text()); | |
| expect(res.ok).toBe(true); | |
| const data = (await res.json()) as any; | |
| expect(Array.isArray(data?.combos)).toBe(true); | |
| const autoCombos = data.combos.filter((c: any) => c.strategy === "auto"); | |
| expect(autoCombos.length).toBeGreaterThanOrEqual(0); | |
| }); | |
| }); | |
| // βββ Scenario 4: OpenClaw Integration ββββββββββββββββββββββββββββ | |
| describe("E2E: OpenClaw Integration", () => { | |
| itCase("should return dynamic provider.order", async () => { | |
| const res = await apiFetch("/api/cli-tools/openclaw/auto-order"); | |
| expect(res.ok).toBe(true); | |
| const data = (await res.json()) as any; | |
| expect(data).toHaveProperty("provider"); | |
| expect(data.provider).toHaveProperty("order"); | |
| expect(Array.isArray(data.provider.order)).toBe(true); | |
| expect(data.provider).toHaveProperty("allow_fallbacks"); | |
| expect(data).toHaveProperty("source"); | |
| }); | |
| }); | |
| // βββ Scenario 5: Stress Test βββββββββββββββββββββββββββββββββββββ | |
| describe("E2E: Stress (100 parallel requests)", () => { | |
| itStress("should handle 100 health checks in <10s", async () => { | |
| const start = Date.now(); | |
| const promises = Array.from({ length: 100 }, (_, i) => | |
| apiFetch("/api/monitoring/health").then((r) => ({ | |
| ok: r.ok, | |
| index: i, | |
| })) | |
| ); | |
| const results = await Promise.allSettled(promises); | |
| const elapsed = Date.now() - start; | |
| const successful = results.filter((r) => r.status === "fulfilled" && r.value.ok).length; | |
| expect(successful).toBeGreaterThanOrEqual(90); // allow 10% failure | |
| expect(elapsed).toBeLessThan(10_000); | |
| }); | |
| itStress("should handle 50 parallel quota checks", async () => { | |
| const promises = Array.from({ length: 50 }, () => | |
| apiFetch("/api/usage/quota").then((r) => r.ok) | |
| ); | |
| const results = await Promise.allSettled(promises); | |
| const successful = results.filter((r) => r.status === "fulfilled" && r.value).length; | |
| expect(successful).toBeGreaterThanOrEqual(40); | |
| }); | |
| }); | |
| // βββ Scenario 6: Security ββββββββββββββββββββββββββββββββββββββββ | |
| describe("E2E: Security", () => { | |
| itCase("should handle missing A2A auth according to server configuration", async () => { | |
| const res = await fetch(`${BASE_URL}/a2a`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| // Intentionally no Authorization header | |
| body: JSON.stringify({ | |
| jsonrpc: "2.0", | |
| id: "sec-1", | |
| method: "message/send", | |
| params: { skill: "quota-management", messages: [] }, | |
| }), | |
| }); | |
| if (API_KEY) { | |
| expect(res.status).toBeGreaterThanOrEqual(401); | |
| return; | |
| } | |
| if (res.status !== 200) { | |
| console.log("SEC-1 FAILED STATUS:", res.status, "BODY:", await res.text()); | |
| } | |
| expect(res.status).toBe(200); | |
| }); | |
| itCase("should handle invalid API keys according to server configuration", async () => { | |
| const res = await fetch(`${BASE_URL}/a2a`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: "Bearer invalid-key-12345", | |
| }, | |
| body: JSON.stringify({ | |
| jsonrpc: "2.0", | |
| id: "sec-2", | |
| method: "message/send", | |
| params: { skill: "quota-management", messages: [] }, | |
| }), | |
| }); | |
| if (API_KEY) { | |
| expect(res.status).toBeGreaterThanOrEqual(401); | |
| return; | |
| } | |
| if (res.status !== 200) { | |
| console.log("SEC-2 FAILED STATUS:", res.status, "BODY:", await res.text()); | |
| } | |
| expect(res.status).toBe(200); | |
| }); | |
| itCase("should not expose internal errors in API responses", async () => { | |
| const res = await apiFetch("/api/monitoring/health", { | |
| method: "PATCH", | |
| }); | |
| // Should return method error without leaking server internals | |
| expect(res.status).toBe(405); | |
| const body = await res.text(); | |
| expect(body.includes("Error:")).toBe(false); | |
| }); | |
| itCase("should validate JSON-RPC request format", async () => { | |
| const res = await apiFetch("/a2a", { | |
| method: "POST", | |
| body: "not-json", | |
| }); | |
| const data = (await res.json()) as any; | |
| if (data.error) { | |
| expect(data.error.code).toBe(-32700); // Parse error | |
| } | |
| }); | |
| }); | |