mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 06:12:10 +03:00
Move chat pipeline validation, circuit breaker execution, proxy resolution, logging, and session header handling into dedicated helpers to keep the SSE handler smaller and easier to verify. Also fix shared API option precedence, rebuild skill version caches after deletions, ignore api.trycloudflare.com false positives, and add rate-limit manager test flush/reset hooks for deterministic coverage. Expand integration and unit coverage across chat routing, auth, cloud sync, skills, executors, streaming, DB helpers, proxy handling, and provider/model utilities.
349 lines
12 KiB
JavaScript
349 lines
12 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-api-keys-route-"));
|
|
process.env.DATA_DIR = TEST_DATA_DIR;
|
|
process.env.API_KEY_SECRET = "test-api-key-secret";
|
|
process.env.CLOUD_URL = "http://cloud.example";
|
|
|
|
const core = await import("../../src/lib/db/core.ts");
|
|
const apiKeysDb = await import("../../src/lib/db/apiKeys.ts");
|
|
const localDb = await import("../../src/lib/localDb.ts");
|
|
const compliance = await import("../../src/lib/compliance/index.ts");
|
|
const listRoute = await import("../../src/app/api/keys/route.ts");
|
|
const keyRoute = await import("../../src/app/api/keys/[id]/route.ts");
|
|
const revealRoute = await import("../../src/app/api/keys/[id]/reveal/route.ts");
|
|
|
|
const MACHINE_ID = "1234567890abcdef";
|
|
|
|
async function resetStorage() {
|
|
delete process.env.ALLOW_API_KEY_REVEAL;
|
|
delete process.env.INITIAL_PASSWORD;
|
|
core.resetDbInstance();
|
|
apiKeysDb.resetApiKeyState();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
async function enableManagementAuth() {
|
|
process.env.INITIAL_PASSWORD = "bootstrap-password";
|
|
await localDb.updateSettings({ requireLogin: true, password: "" });
|
|
}
|
|
|
|
async function createManagementKey() {
|
|
return apiKeysDb.createApiKey("management", MACHINE_ID);
|
|
}
|
|
|
|
function makeRequest(url, { method = "GET", token, body } = {}) {
|
|
const headers = new Headers();
|
|
if (token) {
|
|
headers.set("authorization", `Bearer ${token}`);
|
|
}
|
|
if (body !== undefined) {
|
|
headers.set("content-type", "application/json");
|
|
}
|
|
|
|
return new Request(url, {
|
|
method,
|
|
headers,
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
test.beforeEach(async () => {
|
|
await resetStorage();
|
|
});
|
|
|
|
test.after(async () => {
|
|
await resetStorage();
|
|
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
|
});
|
|
|
|
test("API keys routes require management auth when login protection is enabled", async () => {
|
|
await enableManagementAuth();
|
|
|
|
const unauthenticated = await listRoute.GET(new Request("http://localhost/api/keys"));
|
|
const invalidToken = await listRoute.GET(
|
|
new Request("http://localhost/api/keys", {
|
|
headers: { authorization: "Bearer sk-invalid" },
|
|
})
|
|
);
|
|
|
|
const unauthenticatedBody = await unauthenticated.json();
|
|
const invalidTokenBody = await invalidToken.json();
|
|
|
|
assert.equal(unauthenticated.status, 401);
|
|
assert.equal(unauthenticatedBody.error.message, "Authentication required");
|
|
assert.equal(invalidToken.status, 403);
|
|
assert.equal(invalidTokenBody.error.message, "Invalid management token");
|
|
});
|
|
|
|
test("POST /api/keys creates a key, preserves special characters, and persists noLog", async () => {
|
|
await enableManagementAuth();
|
|
const authKey = await createManagementKey();
|
|
const response = await listRoute.POST(
|
|
makeRequest("http://localhost/api/keys", {
|
|
method: "POST",
|
|
token: authKey.key,
|
|
body: { name: "Key / Prod #1", noLog: true },
|
|
})
|
|
);
|
|
const body = await response.json();
|
|
const stored = await apiKeysDb.getApiKeyById(body.id);
|
|
|
|
assert.equal(response.status, 201);
|
|
assert.equal(body.name, "Key / Prod #1");
|
|
assert.equal(body.noLog, true);
|
|
assert.match(body.key, /^sk-[a-z0-9-]+/i);
|
|
assert.equal(stored?.noLog, true);
|
|
assert.equal(compliance.isNoLog(body.id), true);
|
|
});
|
|
|
|
test("POST /api/keys validates missing and oversized names", async () => {
|
|
await enableManagementAuth();
|
|
const authKey = await createManagementKey();
|
|
|
|
const missingName = await listRoute.POST(
|
|
makeRequest("http://localhost/api/keys", {
|
|
method: "POST",
|
|
token: authKey.key,
|
|
body: {},
|
|
})
|
|
);
|
|
const oversizedName = await listRoute.POST(
|
|
makeRequest("http://localhost/api/keys", {
|
|
method: "POST",
|
|
token: authKey.key,
|
|
body: { name: "x".repeat(201) },
|
|
})
|
|
);
|
|
|
|
assert.equal(missingName.status, 400);
|
|
assert.equal(oversizedName.status, 400);
|
|
});
|
|
|
|
test("POST /api/keys returns a server error for malformed JSON payloads", async () => {
|
|
await enableManagementAuth();
|
|
const authKey = await createManagementKey();
|
|
|
|
const response = await listRoute.POST(
|
|
new Request("http://localhost/api/keys", {
|
|
method: "POST",
|
|
headers: {
|
|
authorization: `Bearer ${authKey.key}`,
|
|
"content-type": "application/json",
|
|
},
|
|
body: "{",
|
|
})
|
|
);
|
|
const body = await response.json();
|
|
|
|
assert.equal(response.status, 500);
|
|
assert.equal(body.error, "Failed to create key");
|
|
});
|
|
|
|
test("GET /api/keys lists masked keys with pagination and GET /api/keys/[id] stays masked", async () => {
|
|
await enableManagementAuth();
|
|
const authKey = await createManagementKey();
|
|
const createdA = await apiKeysDb.createApiKey("Alpha", MACHINE_ID);
|
|
const createdB = await apiKeysDb.createApiKey("Beta", MACHINE_ID);
|
|
|
|
const listResponse = await listRoute.GET(
|
|
makeRequest("http://localhost/api/keys?limit=1&offset=1", {
|
|
token: authKey.key,
|
|
})
|
|
);
|
|
const getResponse = await keyRoute.GET(
|
|
makeRequest(`http://localhost/api/keys/${createdB.id}`, { token: authKey.key }),
|
|
{ params: Promise.resolve({ id: createdB.id }) }
|
|
);
|
|
|
|
const listBody = await listResponse.json();
|
|
const getBody = await getResponse.json();
|
|
|
|
assert.equal(listResponse.status, 200);
|
|
assert.equal(listBody.total, 3);
|
|
assert.equal(listBody.keys.length, 1);
|
|
assert.equal(listBody.keys[0].id, createdA.id);
|
|
assert.notEqual(listBody.keys[0].key, createdA.key);
|
|
assert.match(listBody.keys[0].key, /\*{4}/);
|
|
|
|
assert.equal(getResponse.status, 200);
|
|
assert.equal(getBody.id, createdB.id);
|
|
assert.notEqual(getBody.key, createdB.key);
|
|
assert.match(getBody.key, /\*{4}/);
|
|
});
|
|
|
|
test("GET /api/keys falls back to default pagination for invalid query params", async () => {
|
|
await enableManagementAuth();
|
|
const authKey = await createManagementKey();
|
|
await apiKeysDb.createApiKey("Alpha", MACHINE_ID);
|
|
await apiKeysDb.createApiKey("Beta", MACHINE_ID);
|
|
|
|
const response = await listRoute.GET(
|
|
makeRequest("http://localhost/api/keys?limit=0&offset=-25", {
|
|
token: authKey.key,
|
|
})
|
|
);
|
|
const body = await response.json();
|
|
|
|
assert.equal(response.status, 200);
|
|
assert.equal(body.total, 3);
|
|
assert.equal(body.keys.length, 3);
|
|
assert.equal(body.keys[0].name, "management");
|
|
});
|
|
|
|
test("POST /api/keys triggers cloud sync when cloud mode is enabled", async () => {
|
|
await enableManagementAuth();
|
|
await localDb.updateSettings({ cloudEnabled: true });
|
|
const authKey = await createManagementKey();
|
|
const originalFetch = globalThis.fetch;
|
|
const calls = [];
|
|
globalThis.fetch = async (url, options = {}) => {
|
|
calls.push({ url, options });
|
|
return Response.json({ changes: { apiKeys: 1 } });
|
|
};
|
|
|
|
try {
|
|
const response = await listRoute.POST(
|
|
makeRequest("http://localhost/api/keys", {
|
|
method: "POST",
|
|
token: authKey.key,
|
|
body: { name: "Cloud Synced Key" },
|
|
})
|
|
);
|
|
const body = await response.json();
|
|
const syncPayload = JSON.parse(calls[0].options.body);
|
|
|
|
assert.equal(response.status, 201);
|
|
assert.equal(body.name, "Cloud Synced Key");
|
|
assert.equal(calls.length, 1);
|
|
assert.match(String(calls[0].url), /^http:\/\/cloud\.example\/sync\//);
|
|
assert.ok(Array.isArray(syncPayload.providers));
|
|
assert.ok(Array.isArray(syncPayload.apiKeys));
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test("GET /api/keys/[id] returns 404 for an unknown key and reveal is gated by the feature flag", async () => {
|
|
await enableManagementAuth();
|
|
const authKey = await createManagementKey();
|
|
const created = await apiKeysDb.createApiKey("Reveal Target", MACHINE_ID);
|
|
|
|
const missingResponse = await keyRoute.GET(
|
|
makeRequest("http://localhost/api/keys/missing", { token: authKey.key }),
|
|
{ params: Promise.resolve({ id: "missing" }) }
|
|
);
|
|
const revealDisabled = await revealRoute.GET(
|
|
makeRequest(`http://localhost/api/keys/${created.id}/reveal`, { token: authKey.key }),
|
|
{ params: Promise.resolve({ id: created.id }) }
|
|
);
|
|
|
|
process.env.ALLOW_API_KEY_REVEAL = "true";
|
|
const revealEnabled = await revealRoute.GET(
|
|
makeRequest(`http://localhost/api/keys/${created.id}/reveal`, { token: authKey.key }),
|
|
{ params: Promise.resolve({ id: created.id }) }
|
|
);
|
|
|
|
const missingBody = await missingResponse.json();
|
|
const revealDisabledBody = await revealDisabled.json();
|
|
const revealEnabledBody = await revealEnabled.json();
|
|
|
|
assert.equal(missingResponse.status, 404);
|
|
assert.equal(missingBody.error, "Key not found");
|
|
assert.equal(revealDisabled.status, 403);
|
|
assert.equal(revealDisabledBody.error, "API key reveal is disabled");
|
|
assert.equal(revealEnabled.status, 200);
|
|
assert.equal(revealEnabledBody.key, created.key);
|
|
});
|
|
|
|
test("PATCH /api/keys/[id] updates permissions and rejects invalid payloads", async () => {
|
|
await enableManagementAuth();
|
|
const authKey = await createManagementKey();
|
|
const created = await apiKeysDb.createApiKey("Mutable", MACHINE_ID);
|
|
const patchResponse = await keyRoute.PATCH(
|
|
makeRequest(`http://localhost/api/keys/${created.id}`, {
|
|
method: "PATCH",
|
|
token: authKey.key,
|
|
body: {
|
|
noLog: true,
|
|
allowedModels: ["gpt-4.1-mini"],
|
|
allowedConnections: [],
|
|
isActive: false,
|
|
maxSessions: 2,
|
|
},
|
|
}),
|
|
{ params: Promise.resolve({ id: created.id }) }
|
|
);
|
|
const invalidJsonResponse = await keyRoute.PATCH(
|
|
new Request(`http://localhost/api/keys/${created.id}`, {
|
|
method: "PATCH",
|
|
headers: {
|
|
authorization: `Bearer ${authKey.key}`,
|
|
"content-type": "application/json",
|
|
},
|
|
body: "{",
|
|
}),
|
|
{ params: Promise.resolve({ id: created.id }) }
|
|
);
|
|
const missingKeyResponse = await keyRoute.PATCH(
|
|
makeRequest("http://localhost/api/keys/missing", {
|
|
method: "PATCH",
|
|
token: authKey.key,
|
|
body: { noLog: false },
|
|
}),
|
|
{ params: Promise.resolve({ id: "missing" }) }
|
|
);
|
|
|
|
const patchBody = await patchResponse.json();
|
|
const invalidJsonBody = await invalidJsonResponse.json();
|
|
const missingKeyBody = await missingKeyResponse.json();
|
|
const updated = await apiKeysDb.getApiKeyById(created.id);
|
|
|
|
assert.equal(patchResponse.status, 200);
|
|
assert.equal(patchBody.noLog, true);
|
|
assert.equal(patchBody.isActive, false);
|
|
assert.equal(patchBody.maxSessions, 2);
|
|
assert.deepEqual(updated?.allowedModels, ["gpt-4.1-mini"]);
|
|
assert.equal(updated?.noLog, true);
|
|
assert.equal(updated?.isActive, false);
|
|
assert.equal(invalidJsonResponse.status, 400);
|
|
assert.equal(invalidJsonBody.error.message, "Invalid request");
|
|
assert.equal(missingKeyResponse.status, 404);
|
|
assert.equal(missingKeyBody.error, "Key not found");
|
|
});
|
|
|
|
test("DELETE /api/keys/[id] removes keys and reports missing resources", async () => {
|
|
await enableManagementAuth();
|
|
const authKey = await createManagementKey();
|
|
const created = await apiKeysDb.createApiKey("Disposable", MACHINE_ID);
|
|
|
|
const deleteResponse = await keyRoute.DELETE(
|
|
makeRequest(`http://localhost/api/keys/${created.id}`, {
|
|
method: "DELETE",
|
|
token: authKey.key,
|
|
}),
|
|
{ params: Promise.resolve({ id: created.id }) }
|
|
);
|
|
const missingDeleteResponse = await keyRoute.DELETE(
|
|
makeRequest("http://localhost/api/keys/missing", {
|
|
method: "DELETE",
|
|
token: authKey.key,
|
|
}),
|
|
{ params: Promise.resolve({ id: "missing" }) }
|
|
);
|
|
|
|
const deleteBody = await deleteResponse.json();
|
|
const missingDeleteBody = await missingDeleteResponse.json();
|
|
|
|
assert.equal(deleteResponse.status, 200);
|
|
assert.equal(deleteBody.message, "Key deleted successfully");
|
|
assert.equal(await apiKeysDb.getApiKeyById(created.id), null);
|
|
assert.equal(missingDeleteResponse.status, 404);
|
|
assert.equal(missingDeleteBody.error, "Key not found");
|
|
});
|